iOS开发基础77-一像素线(One Pixel Line)的几种实现方式

iOS 一像素线(One Pixel Line)完全指南:从点像素原理到封装实践与常见坑

一像素线是 iOS UI 开发中最常见的元素之一——分隔线、边框、下划线都离不开它。但很多人对"一像素"的理解并不准确,直接写 height = 1 画出来的其实是 2 像素甚至 3 像素的粗线;用 Core Graphics 画的线经常模糊发虚。本文从最基础的"点 vs 像素"讲起,系统梳理一像素线的所有实现方式、封装技巧和常见坑。


一、核心概念:点(Point)vs 像素(Pixel)

在讲怎么画一像素线之前,必须先搞懂两个最基本的概念,否则一切都是空谈。

1. 点和像素的区别

概念 英文 说明 举例
点 Point(pt) 逻辑单位,iOS 布局用的单位,和屏幕分辨率无关 iPhone 屏幕宽度 375pt
像素 Pixel(px) 物理单位,屏幕实际的发光点,和屏幕分辨率相关 iPhone 屏幕宽度 750px(@2x)或 1125px(@3x)

iOS 开发中,所有布局(frame、constraint、center)用的都是点(pt),不是像素。你写 view.frame = CGRectMake(0, 0, 100, 1),这里的 1 是 1 点,不是 1 像素。

2. 屏幕缩放因子(scale)

Retina 屏幕用多个物理像素来显示一个逻辑点,这个比例就是 scale:

scale = 物理像素数 / 逻辑点数
1 像素 = 1.0 / scale 点
设备类型 scale 1 像素等于多少点 代表设备
非 Retina(@1x) 1.0 1.0 pt iPhone 3GS 及以前(已淘汰)
Retina(@2x) 2.0 0.5 pt iPhone 4~8、iPhone XR、iPhone SE、iPad Air/Pro
Retina HD(@3x) 3.0 0.333 pt iPhone 6 Plus~8 Plus、iPhone X~15 Pro Max

获取 scale:

CGFloat scale = [UIScreen mainScreen].scale; // 2.0 或 3.0

3. 为什么一像素线要写 1.0 / scale?

如果你写 height = 1(1 点):

  • 在 @2x 屏幕上,实际渲染为 2 像素(1pt × 2 = 2px)。
  • 在 @3x 屏幕上,实际渲染为 3 像素(1pt × 3 = 3px)。

这不是一像素线,是两像素或三像素的粗线!

正确的一像素线高度:

CGFloat onePixel = 1.0 / [UIScreen mainScreen].scale;
// @2x: 0.5pt → 实际 1px
// @3x: 0.333pt → 实际 1px

一句话总结:iOS 布局用点,一像素 = 1.0 / scale 点。写 height = 1 画出来的是 2~3 像素的粗线。


二、方法一:UIView + Auto Layout / Frame(最常用)

这是实际开发中最常用的方式,简单直接,支持自动布局。

1. Auto Layout 实现

UIView *line = [[UIView alloc] init];
line.backgroundColor = [UIColor colorWithWhite:0.85 alpha:1.0]; // 分隔线常用浅灰色
line.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:line];

CGFloat onePixel = 1.0 / [UIScreen mainScreen].scale;

[NSLayoutConstraint activateConstraints:@[
    [line.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:16],
    [line.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-16],
    [line.heightAnchor constraintEqualToConstant:onePixel],
    [line.topAnchor constraintEqualToAnchor:self.view.topAnchor constant:100]
]];

2. Frame 实现

CGFloat onePixel = 1.0 / [UIScreen mainScreen].scale;
UIView *line = [[UIView alloc] initWithFrame:CGRectMake(16, 100, self.view.bounds.size.width - 32, onePixel)];
line.backgroundColor = [UIColor colorWithWhite:0.85 alpha:1.0];
[self.view addSubview:line];

3. 垂直线(竖线)

CGFloat onePixel = 1.0 / [UIScreen mainScreen].scale;
UIView *verticalLine = [[UIView alloc] initWithFrame:CGRectMake(100, 0, onePixel, 200)];
verticalLine.backgroundColor = [UIColor colorWithWhite:0.85 alpha:1.0];
[self.view addSubview:verticalLine];

4. 优缺点

优点 缺点
简单直接,代码量少 多一个 UIView 层级(但影响可忽略)
支持 Auto Layout,自动适配旋转/布局变化 大量使用时视图层级较深
可以设置 backgroundColor、alpha 等属性 -
可以加点击事件(虽然一般不需要) -

实际开发中 90% 的一像素线都用这种方式,简单可靠,支持自动布局。


三、方法二:CALayer(高性能)

直接用 CALayer 画线,比 UIView 更轻量(没有事件响应、没有布局系统),性能更好。

1. 基本实现

CGFloat onePixel = 1.0 / [UIScreen mainScreen].scale;

CALayer *lineLayer = [CALayer layer];
lineLayer.backgroundColor = [UIColor colorWithWhite:0.85 alpha:1.0].CGColor;
lineLayer.frame = CGRectMake(16, 100, self.view.bounds.size.width - 32, onePixel);
[self.view.layer addSublayer:lineLayer];

2. 重要注意事项:CALayer 不支持 Auto Layout

CALayer 的 frame 不会随父视图的自动布局自动更新。如果父视图尺寸变化(如屏幕旋转、约束更新),layer 的 frame 不会自动调整,需要手动更新:

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    
    // 手动更新 layer 的 frame
    CGFloat onePixel = 1.0 / [UIScreen mainScreen].scale;
    self.lineLayer.frame = CGRectMake(16, 100, self.view.bounds.size.width - 32, onePixel);
}

或者封装一个自定义 UIView,在 layoutSubviews 中管理内部的 layer:

@interface OnePixelLineView : UIView
@property (nonatomic, strong) CALayer *lineLayer;
@property (nonatomic, strong) UIColor *lineColor;
@end

@implementation OnePixelLineView

- (instancetype)init {
    self = [super init];
    if (self) {
        self.lineLayer = [CALayer layer];
        [self.layer addSublayer:self.lineLayer];
    }
    return self;
}

- (void)layoutSubviews {
    [super layoutSubviews];
    CGFloat onePixel = 1.0 / [UIScreen mainScreen].scale;
    self.lineLayer.frame = CGRectMake(0, 0, self.bounds.size.width, onePixel);
    self.lineLayer.backgroundColor = self.lineColor.CGColor;
}

@end

3. 优缺点

优点 缺点
比 UIView 轻量,性能略好 不支持 Auto Layout,需要手动更新 frame
不增加视图层级(只增加 layer) 不能加点击事件
适合大量线条的场景(如复杂表格) 代码稍多

适用场景:需要大量线条、对性能敏感的场景;或者已经在操作 layer 的自定义视图中。一般场景用 UIView 更方便。


四、方法三:Core Graphics 绘制(自定义视图)

在自定义视图的 drawRect: 中用 Core Graphics 直接绘制,适合完全自定义的绘图场景。

1. 填充矩形方式(推荐,最清晰)

- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGFloat onePixel = 1.0 / [UIScreen mainScreen].scale;
    
    // 在顶部绘制一像素线
    CGRect lineRect = CGRectMake(0, 0, self.bounds.size.width, onePixel);
    CGContextSetFillColorWithColor(context, [UIColor colorWithWhite:0.85 alpha:1.0].CGColor);
    CGContextFillRect(context, lineRect);
}

2. 描边路径方式

- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGFloat onePixel = 1.0 / [UIScreen mainScreen].scale;
    
    CGContextSetStrokeColorWithColor(context, [UIColor colorWithWhite:0.85 alpha:1.0].CGColor);
    CGContextSetLineWidth(context, onePixel);
    
    // 注意:描边时坐标要偏移 0.5 像素,否则线会模糊(见后文"常见坑")
    CGFloat offset = onePixel / 2.0;
    CGContextMoveToPoint(context, 0, offset);
    CGContextAddLineToPoint(context, self.bounds.size.width, offset);
    CGContextStrokePath(context);
}

3. 优缺点

优点 缺点
完全控制绘制过程,最灵活 需要自定义视图,代码量最大
不增加视图层级 drawRect: 有性能开销(不适合频繁刷新)
适合复杂的自定义绘图 描边方式容易模糊(需要坐标偏移)
可以绘制任意形状的线(虚线、斜线等) -

适用场景:自定义视图中已经在 drawRect: 里绘制其他内容,顺便画一条线;或者需要绘制虚线、斜线等特殊线条。


五、方法四:系统自带的分割线

很多场景不需要自己画线,系统已经提供了分割线 API。

1. UITableView 的 separator

// 显示分割线
self.tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine;
self.tableView.separatorColor = [UIColor colorWithWhite:0.85 alpha:1.0];

// 让分割线满宽(默认有左边缩进)
self.tableView.separatorInset = UIEdgeInsetsZero;
// iOS 8+ 还需要设置 layoutMargins
self.tableView.layoutMargins = UIEdgeInsetsZero;

// 在 cell 中
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
    cell.layoutMargins = UIEdgeInsetsZero;
}

注意:UITableView 的 separator 本身就是一像素线(系统自动处理了 scale),不需要自己再画。

2. 隐藏 UITableView 多余的分割线

// 底部多余的分割线用空的 tableFooterView 隐藏
self.tableView.tableFooterView = [[UIView alloc] init];

3. UIStackView 的 spacing

用 UIStackView 布局时,可以用 spacing 做分隔,但 spacing 是空白不是线。如果需要线,可以在 stackView 中插入一个一像素高的 UIView。

4. UINavigationBar / UITabBar 的分隔线

导航栏底部和标签栏顶部的系统分隔线,可以通过设置 shadowImage 自定义或隐藏:

// 隐藏导航栏底部分隔线
[self.navigationController.navigationBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
[self.navigationController.navigationBar setShadowImage:[UIImage new]];

// 隐藏标签栏顶部分隔线
[self.tabBarController.tabBar setBackgroundImage:[UIImage new]];
[self.tabBarController.tabBar setShadowImage:[UIImage new]];

六、实际开发中的封装(重点)

实际项目中不可能每次都写 1.0 / [UIScreen mainScreen].scale,通常会封装成宏、常量或工具方法。

1. 宏定义 / 常量

// PrefixHeader.pch 或 Constants.h 中定义
#define kOnePixel (1.0 / [UIScreen mainScreen].scale)

// 或者用常量(更推荐,宏在某些场景有问题)
static inline CGFloat OnePixel() {
    return 1.0 / [UIScreen mainScreen].scale;
}

使用:

UIView *line = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, kOnePixel)];
line.backgroundColor = [UIColor colorWithWhite:0.85 alpha:1.0];

2. UIView 分类(最常用)

给 UIView 加一个分类,快速添加顶/底/左/右的一像素线:

// UIView+OnePixelLine.h
@interface UIView (OnePixelLine)

/// 添加顶部分割线
- (UIView *)addTopLineWithColor:(UIColor *)color;
- (UIView *)addTopLineWithColor:(UIColor *)color leftInset:(CGFloat)leftInset rightInset:(CGFloat)rightInset;

/// 添加底部分割线
- (UIView *)addBottomLineWithColor:(UIColor *)color;
- (UIView *)addBottomLineWithColor:(UIColor *)color leftInset:(CGFloat)leftInset rightInset:(CGFloat)rightInset;

/// 添加左侧分割线
- (UIView *)addLeftLineWithColor:(UIColor *)color;

/// 添加右侧分割线
- (UIView *)addRightLineWithColor:(UIColor *)color;

@end
// UIView+OnePixelLine.m
#import "UIView+OnePixelLine.h"

@implementation UIView (OnePixelLine)

- (UIView *)addTopLineWithColor:(UIColor *)color {
    return [self addTopLineWithColor:color leftInset:0 rightInset:0];
}

- (UIView *)addTopLineWithColor:(UIColor *)color leftInset:(CGFloat)leftInset rightInset:(CGFloat)rightInset {
    UIView *line = [[UIView alloc] init];
    line.backgroundColor = color;
    line.translatesAutoresizingMaskIntoConstraints = NO;
    [self addSubview:line];
    
    CGFloat onePixel = 1.0 / [UIScreen mainScreen].scale;
    [NSLayoutConstraint activateConstraints:@[
        [line.topAnchor constraintEqualToAnchor:self.topAnchor],
        [line.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:leftInset],
        [line.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:-rightInset],
        [line.heightAnchor constraintEqualToConstant:onePixel]
    ]];
    
    return line;
}

- (UIView *)addBottomLineWithColor:(UIColor *)color {
    return [self addBottomLineWithColor:color leftInset:0 rightInset:0];
}

- (UIView *)addBottomLineWithColor:(UIColor *)color leftInset:(CGFloat)leftInset rightInset:(CGFloat)rightInset {
    UIView *line = [[UIView alloc] init];
    line.backgroundColor = color;
    line.translatesAutoresizingMaskIntoConstraints = NO;
    [self addSubview:line];
    
    CGFloat onePixel = 1.0 / [UIScreen mainScreen].scale;
    [NSLayoutConstraint activateConstraints:@[
        [line.bottomAnchor constraintEqualToAnchor:self.bottomAnchor],
        [line.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:leftInset],
        [line.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:-rightInset],
        [line.heightAnchor constraintEqualToConstant:onePixel]
    ]];
    
    return line;
}

- (UIView *)addLeftLineWithColor:(UIColor *)color {
    UIView *line = [[UIView alloc] init];
    line.backgroundColor = color;
    line.translatesAutoresizingMaskIntoConstraints = NO;
    [self addSubview:line];
    
    CGFloat onePixel = 1.0 / [UIScreen mainScreen].scale;
    [NSLayoutConstraint activateConstraints:@[
        [line.leadingAnchor constraintEqualToAnchor:self.leadingAnchor],
        [line.topAnchor constraintEqualToAnchor:self.topAnchor],
        [line.bottomAnchor constraintEqualToAnchor:self.bottomAnchor],
        [line.widthAnchor constraintEqualToConstant:onePixel]
    ]];
    
    return line;
}

- (UIView *)addRightLineWithColor:(UIColor *)color {
    UIView *line = [[UIView alloc] init];
    line.backgroundColor = color;
    line.translatesAutoresizingMaskIntoConstraints = NO;
    [self addSubview:line];
    
    CGFloat onePixel = 1.0 / [UIScreen mainScreen].scale;
    [NSLayoutConstraint activateConstraints:@[
        [line.trailingAnchor constraintEqualToAnchor:self.trailingAnchor],
        [line.topAnchor constraintEqualToAnchor:self.topAnchor],
        [line.bottomAnchor constraintEqualToAnchor:self.bottomAnchor],
        [line.widthAnchor constraintEqualToConstant:onePixel]
    ]];
    
    return line;
}

@end

使用:

// 给 cell 添加底部分割线,左右缩进 16
[cell.contentView addBottomLineWithColor:[UIColor colorWithWhite:0.85 alpha:1.0] leftInset:16 rightInset:16];

// 给按钮添加顶部分割线
[self.button addTopLineWithColor:[UIColor colorWithWhite:0.85 alpha:1.0]];

这是实际项目中最推荐的方式,封装一次,到处使用,代码简洁且统一。

3. 自定义 OnePixelLine 视图

如果需要一个可复用的一像素线视图(支持 xib/storyboard),可以自定义:

@interface OnePixelLineView : UIView
@property (nonatomic, strong) IBInspectable UIColor *lineColor;
@property (nonatomic, assign) IBInspectable BOOL isHorizontal; // YES=横线,NO=竖线
@end

@implementation OnePixelLineView

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        self.backgroundColor = [UIColor clearColor];
        _lineColor = [UIColor colorWithWhite:0.85 alpha:1.0];
        _isHorizontal = YES;
    }
    return self;
}

- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGFloat onePixel = 1.0 / [UIScreen mainScreen].scale;
    
    CGContextSetFillColorWithColor(context, self.lineColor.CGColor);
    
    if (self.isHorizontal) {
        CGContextFillRect(context, CGRectMake(0, 0, rect.size.width, onePixel));
    } else {
        CGContextFillRect(context, CGRectMake(0, 0, onePixel, rect.size.height));
    }
}

@end

七、常见问题与坑(重点)

坑 1:线模糊、发虚、颜色比预期浅

这是最常见的问题,尤其是用 Core Graphics 描边(stroke)画线时。

原因:Core Graphics 默认开启抗锯齿(Anti-Aliasing)。如果线的位置正好在两个像素的中间,GPU 会用半透明的颜色填充两侧的像素,导致线看起来模糊、变粗、颜色变浅。

解决方法:

方法一:关闭抗锯齿(适合水平线/垂直线,不适合斜线/曲线)

CGContextSetShouldAntialias(context, NO); // 关闭抗锯齿
// 画线...
CGContextSetShouldAntialias(context, YES); // 画完后恢复

方法二:坐标偏移 0.5 像素(描边时推荐)

描边时,线的中心在指定坐标上。如果坐标是整数,线的中心正好在像素边界上,会横跨两个像素,导致模糊。将坐标偏移 0.5 像素,让线的中心正好在像素中心:

CGFloat onePixel = 1.0 / [UIScreen mainScreen].scale;
CGFloat offset = onePixel / 2.0; // 0.25pt(@2x)或 0.1667pt(@3x)

CGContextMoveToPoint(context, 0, offset);
CGContextAddLineToPoint(context, self.bounds.size.width, offset);
CGContextStrokePath(context);

方法三:用填充(fill)代替描边(stroke)

填充矩形不会有模糊问题,因为矩形的边界正好对齐像素:

// 推荐用填充,不要用描边
CGRect lineRect = CGRectMake(0, 0, width, onePixel);
CGContextFillRect(context, lineRect);

总结:用 UIView/CALayer 设置 backgroundColor 一般不会模糊;用 Core Graphics 时优先用 fill 而不是 stroke;如果必须用 stroke,坐标偏移 0.5 像素或关闭抗锯齿。

坑 2:线不显示

可能原因:

  1. 高度写成了 0:1.0 / scale 计算错误,或写成了 1 / scale(整数除法,结果为 0)。
    CGFloat onePixel = 1 / [UIScreen mainScreen].scale; // ❌ 整数除法,1/2=0
    CGFloat onePixel = 1.0 / [UIScreen mainScreen].scale; // ✅ 浮点除法
    
  2. 颜色和背景色相同:线的颜色和父视图背景色一样,看不出来。
  3. 被其他视图遮挡:线的层级在其他视图下面。
  4. CALayer 的 frame 为 0:添加时父视图还没布局完成,frame 计算错误。
  5. hidden = YES 或 alpha = 0:不小心设置了隐藏或透明。

坑 3:CALayer 的线在旋转/布局变化后位置不对

原因:CALayer 不支持 Auto Layout,frame 不会自动更新。

解决:在 layoutSubviews 或 viewDidLayoutSubviews 中手动更新 layer 的 frame,或用自定义 UIView 封装(见方法二)。

坑 4:UITableView 分割线左边有缩进

原因:iOS 7 以后 UITableView 的 separator 默认有左边缩进(15pt 或 16pt)。

解决:

// iOS 7+
self.tableView.separatorInset = UIEdgeInsetsZero;

// iOS 8+ 还需要设置 layoutMargins
self.tableView.layoutMargins = UIEdgeInsetsZero;

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
    cell.layoutMargins = UIEdgeInsetsZero;
}

// iOS 15+ 可能还需要
if (@available(iOS 15.0, *)) {
    self.tableView.sectionHeaderTopPadding = 0;
}

坑 5:在 xib/storyboard 中设置一像素高度

xib/storyboard 中约束的高度只能输入整数,不能直接输入 0.5。

解决方法:

  1. 用代码添加一像素线(推荐)。
  2. 在 xib 中给线一个普通高度约束,然后在代码中修改约束的 constant:
    self.lineHeightConstraint.constant = 1.0 / [UIScreen mainScreen].scale;
    
  3. 用 IBDesignable/IBInspectable 自定义视图(见封装部分)。

坑 6:一像素线在不同设备上粗细不一致

原因:没有用 1.0 / scale,而是硬编码了 0.5 或 1。

解决:永远用 1.0 / [UIScreen mainScreen].scale 动态计算,不要硬编码。

// ❌ 硬编码,@3x 上是 1.5 像素(模糊)
line.frame = CGRectMake(0, 0, width, 0.5);

// ✅ 动态计算,所有设备都是精确 1 像素
CGFloat onePixel = 1.0 / [UIScreen mainScreen].scale;
line.frame = CGRectMake(0, 0, width, onePixel);

坑 7:画虚线

用 Core Graphics 画虚线时,注意线宽和偏移:

- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGFloat onePixel = 1.0 / [UIScreen mainScreen].scale;
    
    CGContextSetStrokeColorWithColor(context, [UIColor colorWithWhite:0.85 alpha:1.0].CGColor);
    CGContextSetLineWidth(context, onePixel);
    
    // 虚线样式:4pt 实线,2pt 空白
    CGFloat lengths[] = {4, 2};
    CGContextSetLineDash(context, 0, lengths, 2);
    
    CGFloat offset = onePixel / 2.0;
    CGContextMoveToPoint(context, 0, offset);
    CGContextAddLineToPoint(context, self.bounds.size.width, offset);
    CGContextStrokePath(context);
}

八、性能对比与最佳实践

1. 各种方法的性能对比

方法 性能 易用性 Auto Layout 适用场景
UIView + backgroundColor 好 最好 支持 绝大多数场景(推荐)
CALayer 最好 一般 不支持,需手动更新 大量线条、性能敏感场景
Core Graphics fill 好 较差 不支持,需自定义视图 自定义视图中绘制
Core Graphics stroke 一般(易模糊) 差 不支持 斜线、虚线等特殊线条
系统 separator 最好 最好 自动 UITableView 列表

2. 最佳实践

  1. 优先用系统 API:UITableView 用 separator,不要自己画。
  2. 一般场景用 UIView:UIView + backgroundColor + 1.0/scale 高度,简单可靠,支持 Auto Layout。
  3. 封装成工具:项目中封装 UIView+OnePixelLine 分类或宏定义,统一使用。
  4. Core Graphics 用 fill 不用 stroke:fill 不会模糊,stroke 容易模糊(需要坐标偏移)。
  5. 永远动态计算:1.0 / [UIScreen mainScreen].scale,不要硬编码 0.5。
  6. CALayer 注意布局更新:在 layoutSubviews 中手动更新 frame。
  7. 大量线条用 CALayer:如复杂表格、日历等大量线条场景,CALayer 性能更好。

九、Swift 版本对照

1. 一像素计算

let onePixel = 1.0 / UIScreen.main.scale

2. UIView + Auto Layout

let line = UIView()
line.backgroundColor = UIColor(white: 0.85, alpha: 1.0)
line.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(line)

let onePixel = 1.0 / UIScreen.main.scale
NSLayoutConstraint.activate([
    line.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
    line.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
    line.heightAnchor.constraint(equalToConstant: onePixel),
    line.topAnchor.constraint(equalTo: view.topAnchor, constant: 100)
])

3. CALayer

let onePixel = 1.0 / UIScreen.main.scale
let lineLayer = CALayer()
lineLayer.backgroundColor = UIColor(white: 0.85, alpha: 1.0).cgColor
lineLayer.frame = CGRect(x: 16, y: 100, width: view.bounds.width - 32, height: onePixel)
view.layer.addSublayer(lineLayer)

4. Core Graphics

override func draw(_ rect: CGRect) {
    guard let context = UIGraphicsGetCurrentContext() else { return }
    let onePixel = 1.0 / UIScreen.main.scale
    
    context.setFillColor(UIColor(white: 0.85, alpha: 1.0).cgColor)
    context.fill(CGRect(x: 0, y: 0, width: rect.width, height: onePixel))
}

5. UIView 分类(Swift)

extension UIView {
    @discardableResult
    func addBottomLine(color: UIColor, leftInset: CGFloat = 0, rightInset: CGFloat = 0) -> UIView {
        let line = UIView()
        line.backgroundColor = color
        line.translatesAutoresizingMaskIntoConstraints = false
        addSubview(line)
        
        let onePixel = 1.0 / UIScreen.main.scale
        NSLayoutConstraint.activate([
            line.bottomAnchor.constraint(equalTo: bottomAnchor),
            line.leadingAnchor.constraint(equalTo: leadingAnchor, constant: leftInset),
            line.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -rightInset),
            line.heightAnchor.constraint(equalToConstant: onePixel)
        ])
        
        return line
    }
}

// 使用
cell.contentView.addBottomLine(color: UIColor(white: 0.85, alpha: 1.0), leftInset: 16, rightInset: 16)

十、总结

  • 核心概念:iOS 布局用点(pt),屏幕物理单位是像素(px),Retina 屏幕 scale=2 或 3。一像素 = 1.0 / scale 点(@2x 是 0.5pt,@3x 是 0.333pt)。写 height=1 画出来的是 2~3 像素的粗线。
  • 实现方式:
    • UIView + backgroundColor:最常用,简单可靠,支持 Auto Layout,90% 场景用这个。
    • CALayer:性能最好,但不支持 Auto Layout,需手动更新 frame,适合大量线条场景。
    • Core Graphics:在 drawRect: 中绘制,用 fill(填充)不要用 stroke(描边),适合自定义视图和特殊线条(虚线、斜线)。
    • 系统 API:UITableView 的 separator 等,优先使用。
  • 封装:项目中封装 UIView+OnePixelLine 分类(添加顶/底/左/右线)或宏定义 kOnePixel,统一使用。
  • 常见坑:
    • 线模糊:Core Graphics 用 stroke 时坐标偏移 0.5 像素,或关闭抗锯齿,或改用 fill。
    • 线不显示:整数除法 1/scale=0,用 1.0/scale。
    • CALayer 不跟随布局:在 layoutSubviews 中手动更新 frame。
    • UITableView 分割线缩进:设置 separatorInset 和 layoutMargins 为 zero。
    • 不同设备粗细不一致:永远动态计算 1.0/scale,不要硬编码 0.5。
  • 最佳实践:优先系统 API → 一般场景 UIView → 大量线条 CALayer → 自定义绘图 Core Graphics;封装统一工具;动态计算一像素高度。

posted @ 2016-03-26 14:35  Mr.陳  阅读(982)  评论(0)    收藏  举报