Swift - UIView绘制1个像素宽度的线

时间:2014-12-09 18:14:52

标签: ios swift uiview drawing line

我试图使用Swift在UIView上画一条线:

CGContextSetLineWidth(context, 1.0)
CGContextBeginPath(context)
    CGContextMoveToPoint(context, x1, y1)
    CGContextAddLineToPoint(context, x2, y2)
CGContextStrokePath(context)

但绘制的线条宽度为2像素。我尝试了另一种方式:

CGContextSetLineWidth(context, 0.5)
CGContextBeginPath(context)
    CGContextMoveToPoint(context, x1, y1)
    CGContextAddLineToPoint(context, x2, y2)
CGContextStrokePath(context)

但结果是一样的。 我做错了什么以及如何使用Swift在UIView上绘制1像素宽的线?

3 个答案:

答案 0 :(得分:9)

这里有两个问题:

  1. CoreGraphics绘图功能在点(屏幕布局的单位,在所有设备上的近似物理大小不变)上工作,而不是以像素为单位。在具有不同屏幕尺度的设备上,每个点的像素数是不同的:iPad 2和iPad mini是iOS 7及更高版本支持的唯一1x设备,iPhone 4,iPad 3,iPad mini 2及更高版本是2x,iPhone 6除外/ 6s / 7 Plus是3x。因此,如果你想要一个设备像素的发际线,你需要在大多数现有设备上使用0.5点线宽(在iPhone 6 Plus上需要0.33点宽度)。

  2. 线条的宽度以一个点的正方形区域为中心。因此,如果您有一条从(10.0,10.0)到(10.0,20.0)的线,它实际上位于x坐标10.0和9.0的像素之间 - 渲染时,抗锯齿将遮蔽10.0和9.0列的像素在50%的线条颜色,而不是100%阴影一列。要解决此问题,您需要定位线条,使其完全位于像素内。 (设备像素,而不是布局点。)

  3. 因此,要获得一个像素的发际线,您需要同时减小线宽并将绘制的点偏移量根据屏幕的比例因子而变化。以下是您的测试用例的扩展:

    // pass in the scale of your UIScreen
    func drawHairline(in context: CGContext, scale: CGFloat, color: CGColor) {
    
        // pick which row/column of pixels to treat as the "center" of a point
        // through which to draw lines -- favor true center for odd scales, or
        // offset to the side for even scales so we fall on pixel boundaries
        let center: CGFloat
        if Int(scale) % 2 == 0 {
            center = 1 / (scale * 2)
        } else {
            center = 0
        }
    
        let offset = 0.5 - center // use the "center" choice to create an offset
        let p1 = CGPoint(x: 50 + offset, y: 50 + offset)
        let p2 = CGPoint(x: 50 + offset, y: 75 + offset)
    
        // draw line of minimal stroke width
        let width = 1 / scale
        context.setLineWidth(width)
        context.setStrokeColor(color)
        context.beginPath()
        context.move(to: p1)
        context.addLine(to: p2)
        context.strokePath()
    }
    

    centerChoice计算概括了必须选择哪个子点像素来绘制线条的问题。您必须绘制点的中心(偏移0.5)以在1x显示器上遮蔽整个像素或仅遮蔽3x显示器上的点的中间像素,但在2x显示器上偏移0.5在两个像素之间这一点构成了一点。因此,对于2x,您必须选择偏移0.25或偏移0.75 - center行通常用于偶数或奇数比例因子。

    注意#1:我更改了你的测试用例以绘制一条垂直线,因为这样更容易看到抗锯齿的效果。对角线无论如何都会得到一些抗锯齿,但是如果宽度恰好在正确的位置,垂直或水平线将不会产生抗锯齿。

    注意#2:iPhone 6 / 6s / 7 Plus的逻辑比例为3.0,物理显示比例约为2.61 - 您可能希望使用screen.scalescreen.nativeScale进行比较让你更好看结果。

答案 1 :(得分:1)

另外 rickster 回答。

您可以在storeboard中使用此类:

/** View that have line with 1px on the top */
class lineView :UIView {

    // pass in the scale of your UIScreen
    func drawHairline(in context: CGContext, scale: CGFloat, color: CGColor) {

        // pick which row/column of pixels to treat as the "center" of a point
        // through which to draw lines -- favor true center for odd scales, or
        // offset to the side for even scales so we fall on pixel boundaries
        let center: CGFloat
        if Int(scale) % 2 == 0 {
            center = 1 / (scale * 2)
        } else {
            center = 0
        }

        let offset = 0.5 - center // use the "center" choice to create an offset
        let p1 = CGPoint(x: 0, y: 0 + offset)
        let p2 = CGPoint(x: 800 + offset, y: 0 + offset)

        // draw line of minimal stroke width
        let width = 1 / scale
        context.setLineWidth(width)
        context.setStrokeColor(color)
        context.beginPath()
        context.move(to: p1)
        context.addLine(to: p2)
        context.strokePath()
    }

    override func draw(_ rect: CGRect) {
        let context = UIGraphicsGetCurrentContext()
        drawHairline(in: context!, scale: 2, color: UIColor.gray.cgColor)
    }
}

答案 2 :(得分:0)

我使用此代码进行1px底线视图。其他示例在x3显示器上工作不佳,例如6 Plus

import UIKit

class ViewWithBottomLine: UIView {
    @IBInspectable var separatorColor: UIColor = Your default color

    override func draw(_ rect: CGRect) {
        super.draw(rect)

        guard let context = UIGraphicsGetCurrentContext() else {
            return
        }

        let scale = UIScreen.main.scale

        let width = 1 / scale
        let offset = width / 2

        context.setLineWidth(width)
        context.setStrokeColor(separatorColor.cgColor)
        context.beginPath()
        context.move(to: CGPoint(x: 0, y: rect.maxY - offset))
        context.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - offset))
        context.strokePath()
    }
}