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

Yur*_*hev 4 drawing line uiview ios swift

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

CGContextSetLineWidth(context, 1.0)
CGContextBeginPath(context)
    CGContextMoveToPoint(context, x1, y1)
    CGContextAddLineToPoint(context, x2, y2)
CGContextStrokePath(context)
Run Code Online (Sandbox Code Playgroud)

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

CGContextSetLineWidth(context, 0.5)
CGContextBeginPath(context)
    CGContextMoveToPoint(context, x1, y1)
    CGContextAddLineToPoint(context, x2, y2)
CGContextStrokePath(context)
Run Code Online (Sandbox Code Playgroud)

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

ric*_*ter 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%阴影一列.要解决此问题,您需要定位线条,使其完全位于像素内.(设备像素,而不是布局点.)

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

// 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()
}
Run Code Online (Sandbox Code Playgroud)

该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.scale对比screen.nativeScale,看看哪个让你更漂亮的效果.


Evg*_*rev 5

我使用这段代码来查看底线为 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()
    }
}
Run Code Online (Sandbox Code Playgroud)