探讨iOS某个像素点是否显示依据,以摸索为什么iOS没适配的应用在iphone6上面线的粗细会不稳定。有的线会变粗。

先说一下系统对某个像素点是否显示的依据:

//    如果单个像素分为10格,如上图,从上往下第5格被填充或从右往左第5格被填充,则该像素就会被显示;实际上系统会划分的更细,不只是10格。

//    下面是横向的测试代码,需要在2倍屏上测试。

UIView *line = [[UIViewalloc]initWithFrame:CGRectMake(0,10,320,0.25)];//显示

line.backgroundColor = [UIColorredColor];

[_baseContentaddSubview:line];

line = [[UIViewalloc]initWithFrame:CGRectMake(0,12,320,0.2)];//不会显示

line.backgroundColor = [UIColorblueColor];

[_baseContentaddSubview:line];

line = [[UIViewalloc]initWithFrame:CGRectMake(0,14.25,320,0.05)];//不会显示

line.backgroundColor = [UIColorblackColor];

[_baseContentaddSubview:line];

line = [[UIViewalloc]initWithFrame:CGRectMake(0,16.2,320,0.05)];//显示

line.backgroundColor = [UIColorgreenColor];

[_baseContentaddSubview:line];

//    下面是纵向的测试代码

line = [[UIViewalloc]initWithFrame:CGRectMake(10,0,0.255,300)];//显示

line.backgroundColor = [UIColorredColor];

[_baseContentaddSubview:line];

line = [[UIViewalloc]initWithFrame:CGRectMake(12,0,0.25,300)];//不会显示

line.backgroundColor = [UIColorblueColor];

[_baseContentaddSubview:line];

line = [[UIViewalloc]initWithFrame:CGRectMake(14.25,0,0.05,300)];//显示

line.backgroundColor = [UIColorblackColor];

[_baseContentaddSubview:line];

line = [[UIViewalloc]initWithFrame:CGRectMake(16.2,0,0.05,300)];//不会显示

line.backgroundColor = [UIColorgreenColor];

[_baseContentaddSubview:line];

总结:当设置的线如果是1px的时候无论起点在哪里,都只会显示1px的线。

但是如果线超过了1px,比如说1.2px。那么起点的位置就有可能导致线变成2px。

//     下面是测试代码,宽度设置相同,上面是1px,下面是2px。也是在2倍屏上

line = [[UIViewalloc]initWithFrame:CGRectMake(18,0,0.6,300)];//1px

line.backgroundColor = [UIColoryellowColor];

[_baseContentaddSubview:line];

line = [[UIViewalloc]initWithFrame:CGRectMake(22.2,0,0.6,300)];//2px

line.backgroundColor = [UIColoryellowColor];

[_baseContentaddSubview:line];

明白了这个道理,就不难理解为什么没适配的应用在iphone6上面线有可能会变粗。因为0.5个点宽的线在iphone6上面线宽像素会变成1.几px。有时候就有可能显示成2px。

对于一个设备最细只能是1pixel。不可能画出0.5pixel的线

retina 屏幕

uiview frame.height = 0.5; 显示出来就是0.5*2.0=1pixel

非retina 屏幕

uiview frame.height = 1; 显示出来就是1*1.0=1pixel

这样就能看到最细的线了。

不使用抗锯齿效果, CGContextSetAllowsAntialiasing(contextRef,NO);即可

一、Point Vs Pixel

iOS中当我们使用Quartz,UIKit,CoreAnimation等框架时,所有的坐标系统采用Point来衡量。系统在实际渲染到设置时会帮助我们处理Point到Pixel的转换。

这样做的好处隔离变化,即我们在布局的事后不需要关注当前设备是否为Retina,直接按照一套坐标系统来布局即可。

实际使用中我们需要牢记下面这一点:

 
  1. One point does not necessarily correspond to one physical pixel.

1 Point的线在非Retina屏幕则是一个像素,在Retina屏幕上则可能是2个或者3个,取决于系统设备的DPI。

iOS系统中,UIScreen,UIView,UIImage,CALayer类都提供相关属性来获取scale factor。

原生的绘制技术天然的帮我们处理了scale factor,例如在drawRect:方法中,UIKit自动的根据当前运行的设备设置了正切的scale factor。所以我们在drawRect: 方法中绘制的任何内容都会被自动缩放到设备的物理屏幕上。

基于以上信息可以看出,我们大部分情况下都不需要去关注pixel,然而存在部分情况需要考虑像素的转化。

例如画1个像素的分割线

看到这个问题你的第一想法可能是,直接根据当前屏幕的缩放因子计算出1 像素线对应的Point,然后设置线宽即可。

代码如下:

 
  1. 1.0f / [UIScreen mainScreen].scale

表面上看着一切正常了,但是通过实际的设备测试你会发现渲染出来的线宽并不是1个像素。

Why?

为了获得良好的视觉效果,绘图系统通常都会采用一个叫“antialiasing(反锯齿)”的技术,iOS也不例外。

显示屏幕有很多小的显示单元组成,可以接单的理解为一个单元就代表一个像素。如果要画一条黑线,条线刚好落在了一列或者一行显示显示单元之内,将会渲染出标准的一个像素的黑线。

但如果线落在了两个行或列的中间时,那么会得到一条“失真”的线,其实是两个像素宽的灰线。

如下图所示:

 
  1. Positions defined by whole-numbered points fall at the midpoint between pixels.
  2. For example, if you draw a one-pixel-wide vertical line from (1.0, 1.0) to (1.0, 10.0),
  3. you get a fuzzy grey line. If you draw a two-pixel-wide line,
  4. you get a solid black line because it fully covers two pixels (one on either side of the specified point).
  5. As a rule, lines that are an odd number of physical pixels wide appear softer than lines with widths
  6. measured in even numbers of physical pixels unless you adjust their position to make them cover pixels fully.

官方解释如上,简单翻译一下:

 
  1. 规定:奇数像素宽度的线在渲染的时候将会表现为柔和的宽度扩展到向上的整数宽度的线,
  2. 除非你手动的调整线的位置,使线刚好落在一行或列的显示单元内。

如何对齐呢?

 
  1. On a low-resolution display (with a scale factor of 1.0), a one-point-wide line
  2. is one pixel wide. To avoid antialiasing when you draw a one-point-wide horizontal or vertical line,
  3. if the line is an odd number of pixels in width, you must offset the position by 0.5 points to
  4. either side of a whole-numbered position. If the line is an even number of points in width,
  5. to avoid a fuzzy line, you must not do so.
  6. On a high-resolution display (with a scale factor of 2.0), a line that is one point wide is
  7. not antialiased at all because it occupies two full pixels (from -0.5 to +0.5).
  8. To draw a line that covers only a single physical pixel, you would need to make it 0.5 points in thickness and offset its position by 0.25 points. A comparison between the two types of screens is shown in Figure 1-4.

翻译一下

 
  1. 在非高清屏上,一个Point对应一个像素。为了防止“antialiasing”导致的奇数像素的线渲染时出现失真,你需要设置偏移0.5 Point。
  2. 在高清屏幕上,要绘制一个像素的线,需要设置线宽为0.5个Point,同事设置偏移为0.25 Point。
  3. 如果线宽为偶数Point的话,则不要去设置偏移,否则线条也会失真。

如下图所示:

看了上述一通解释,我们了解了1像素宽的线条失真的原因,及解决办法。

至此问题貌似都解决了?再想想为什么在非Retina和Retina屏幕上调整位置时值不一样,前者为0.5Point,后者为0.25Point,那么scale为3的6 Plus设备又该调整多少呢?

要回答这个问题,我们需要理解调整多少依旧什么原则。

再回过头来看看这上面的图片,图片中每一格子代表一个像素,而顶部标记的则代码我们布局时的坐标。

可以看到左边的非Retina屏幕,我们要在(3,0)这个位置画一条一个像素宽的竖线时,由于渲染的最小单位是像素,而(3,0)这个坐标恰好位于两个像素中间,此时系统会对坐标3左右两列的像素对填充,为了不至于线显得太宽,为对线的颜色淡化。那么根据上述信息我们可以得出,如果要画出一个像素宽的线,就得把绘制的坐标移动到(2.5, 0)或者(3.5,0)这个位置,这样系统渲染的时候刚好可以填充一列像素,也就是标准的一个像素的线。

基于上面的分析,我们可以得出“Scale为3的6 Plus”设备如果要绘制1个像素宽的线条时,位置调整也应该是0.5像素,对应该的Point计算如下:

 
  1. (1.0f / [UIScreen mainScreen].scale) / 2;

奉上一个画一像素线的一个宏:

 
  1. #define SINGLE_LINE_WIDTH           (1 / [UIScreen mainScreen].scale)
  2. #define SINGLE_LINE_ADJUST_OFFSET   ((1 / [UIScreen mainScreen].scale) / 2)

使用代码如下:

  1. CGFloat xPos = 5;
  2. UIView *view = [[UIView alloc] initWithFrame:CGrect(x - SINGLE_LINE_ADJUST_OFFSET, 0, SINGLE_LINE_WIDTH, 100)];

二、正确的绘制Grid线条

贴上一个写的GridView的代码,代码中对Grid线条的奇数像素做了偏移,防止出现线条模糊的情况。

SvGridView.h

 
  1. //
  2. //  SvGridView.h
  3. //  SvSinglePixel
  4. //
  5. //  Created by xiaoyong.cxy on 6/23/15.
  6. //  Copyright (c) 2015 smileEvday. All rights reserved.
  7. //
  8. #import @interface SvGridView : UIView
  9. /**
  10. * @brief 网格间距,默认30
  11. */
  12. @property (nonatomic, assign) CGFloat   gridSpacing;
  13. /**
  14. * @brief 网格线宽度,默认为1 pixel (1.0f / [UIScreen mainScreen].scale)
  15. */
  16. @property (nonatomic, assign) CGFloat   gridLineWidth;
  17. /**
  18. * @brief 网格颜色,默认蓝色
  19. */
  20. @property (nonatomic, strong) UIColor   *gridColor;
  21. @end

SvGridView.m

 
  1. //
  2. //  SvGridView.m
  3. //  SvSinglePixel
  4. //
  5. //  Created by xiaoyong.cxy on 6/23/15.
  6. //  Copyright (c) 2015 smileEvday. All rights reserved.
  7. //
  8. #import "SvGridView.h"
  9. #define SINGLE_LINE_WIDTH           (1 / [UIScreen mainScreen].scale)
  10. #define SINGLE_LINE_ADJUST_OFFSET   ((1 / [UIScreen mainScreen].scale) / 2)
  11. @implementation SvGridView
  12. @synthesize gridColor = _gridColor;
  13. @synthesize gridSpacing = _gridSpacing;
  14. - (instancetype)initWithFrame:(CGRect)frame
  15. {
  16. self = [super initWithFrame:frame];
  17. if (self) {
  18. self.backgroundColor = [UIColor clearColor];
  19. _gridColor = [UIColor blueColor];
  20. _gridLineWidth = SINGLE_LINE_WIDTH;
  21. _gridSpacing = 30;
  22. }
  23. return self;
  24. }
  25. - (void)setGridColor:(UIColor *)gridColor
  26. {
  27. _gridColor = gridColor;
  28. [self setNeedsDisplay];
  29. }
  30. - (void)setGridSpacing:(CGFloat)gridSpacing
  31. {
  32. _gridSpacing = gridSpacing;
  33. [self setNeedsDisplay];
  34. }
  35. - (void)setGridLineWidth:(CGFloat)gridLineWidth
  36. {
  37. _gridLineWidth = gridLineWidth;
  38. [self setNeedsDisplay];
  39. }
  40. // Only override drawRect: if you perform custom drawing.
  41. // An empty implementation adversely affects performance during animation.
  42. - (void)drawRect:(CGRect)rect
  43. {
  44. CGContextRef context = UIGraphicsGetCurrentContext();
  45. CGContextBeginPath(context);
  46. CGFloat lineMargin = self.gridSpacing;
  47. /**
  48. *  https://developer.apple.com/library/ios/documentation/2DDrawing/Conceptual/DrawingPrintingiOS/GraphicsDrawingOverview/GraphicsDrawingOverview.html
  49. * 仅当要绘制的线宽为奇数像素时,绘制位置需要调整
  50. */
  51. CGFloat pixelAdjustOffset = 0;
  52. if (((int)(self.gridLineWidth * [UIScreen mainScreen].scale) + 1) % 2 == 0) {
  53. pixelAdjustOffset = SINGLE_LINE_ADJUST_OFFSET;
  54. }
  55. CGFloat xPos = lineMargin - pixelAdjustOffset;
  56. CGFloat yPos = lineMargin - pixelAdjustOffset;
  57. while (xPos < self.bounds.size.width) {
  58. CGContextMoveToPoint(context, xPos, 0);
  59. CGContextAddLineToPoint(context, xPos, self.bounds.size.height);
  60. xPos += lineMargin;
  61. }
  62. while (yPos < self.bounds.size.height) {
  63. CGContextMoveToPoint(context, 0, yPos);
  64. CGContextAddLineToPoint(context, self.bounds.size.width, yPos);
  65. yPos += lineMargin;
  66. }
  67. CGContextSetLineWidth(context, self.gridLineWidth);
  68. CGContextSetStrokeColorWithColor(context, self.gridColor.CGColor);
  69. CGContextStrokePath(context);
  70. }
  71. @end

使用方法如下:

 
  1. SvGridView *gridView = [[SvGridView alloc] initWithFrame:self.view.bounds];
  2. gridView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  3. gridView.alpha = 0.6;
  4. gridView.gridColor = [UIColor greenColor];
  5. [self.view addSubview:gridView];

三、一个问题

好了,到这儿本文的全部知识就结束了,最后我还有一个问题。

设计师为什么一定要一个像素的线?

一个像素的线可能在非Retina设备上显示宽度看着合适,在Retina屏幕上显示可能会比较细。是不是一定需要一个像素的线,需要根据情况来处理。

ios上1像素的问题相关推荐

  1. 获得ios屏幕上的像素

    转自answer-Huang的博客   [answer-Huang微博] 原文:http://www.objc.io/issue-3/moving-pixels-onto-the-screen.htm ...

  2. IOS上 关于状态栏的相关设置(UIStatusBar)和preferredStatusBarStyle不执行问题

    转:http://m.blog.csdn.net/blog/zqx654033799/23597509 一.在老版本的iOS中,状态栏永远都是白色风格.而在iOS 7中,我们可以修改每个view co ...

  3. vue valley_独立农场游戏Stardew Valley今日在iOS上大获成功

    vue valley Stardew Valley, the one-man passion project and spiritual successor to 16-bit farming sim ...

  4. iOS 上常用的两个功能:点击屏幕和return退出隐藏键盘和解决虚拟键盘

    原文地址:http://blog.csdn.net/xiaotanyu13/article/details/7711954 iOS上面对键盘的处理很不人性化,所以这些功能都需要自己来实现, 首先是点击 ...

  5. ios snapkit m_如何使用自动布局和SnapKit在iOS上创建漂亮的拉伸布局

    ios snapkit m by Enabled Solutions 由Enabled Solutions 如何使用自动布局和SnapKit在iOS上创建漂亮的拉伸布局 (How to create ...

  6. canvas上的像素操作(图像复制,细调)

    canvas上的像素操作(图像复制,细调) 总结 1.操作对象:ImageData 对象,其实是canvas的像素点集合 2.主要操作: var obj=ctx.getImageData(0,0,10 ...

  7. document.onclick在ios上不触发的解决方法与touchstart点击穿透处理

    document.onclick = function (e) {var e = e ? e : window.event;var tar = e.srcElement || e.target;if ...

  8. 解决 iframe 在 ios 上不能滚动的问题

    解决 iframe 在 ios 上不能滚动的问题 参考文章: (1)解决 iframe 在 ios 上不能滚动的问题 (2)https://www.cnblogs.com/xieze/p/670211 ...

  9. 解决页面使用overflow: scroll在iOS上滑动卡顿的问题

    解决页面使用overflow: scroll在iOS上滑动卡顿的问题 参考文章: (1)解决页面使用overflow: scroll在iOS上滑动卡顿的问题 (2)https://www.cnblog ...

最新文章

  1. 《资安人》:迈向成功SOC之路
  2. Django之路——6 Django的模型层(二)
  3. 简单的Ajax应用实例
  4. 科研入门之在web of science
  5. 编程统计BUF字单元数据中所含1的个数,并将结果存入COUNT单元中。
  6. 系统架构师学习笔记-多媒体基础知识
  7. 《DirectX 9.0 3D游戏开发编程基础》 第一章 初始化Direct3D 读书笔记
  8. 一个Linux USB驱动:USB Skeleton driver - 2.0
  9. js去掉所有html标记
  10. C#中各种字符类型的转化
  11. Vensim软件中文介绍
  12. [填坑]ubuntu16.04安装腾达U12无线网卡驱动
  13. 业务流程图和数据流程图、流程图
  14. wpf自己画表盘_练手WPF(一)——模拟时钟与数字时钟的制作(上)
  15. 微信聊天内容制作生成器微信小程序源码/支持多种制作生成
  16. 计算机操作系统学习笔记
  17. Office等套件一键下载安装和谐
  18. 36-sparkstreaming
  19. 工程经济学复习2.0
  20. Nginx的启动、停止与重启

热门文章

  1. 数据库的入门简单了解
  2. kendryte K210开发:关于MAIX BIT 无法使用 kendryte IDE 下载的问题
  3. Predis的一些操作汇总
  4. IDEA中建包时如何把包分开
  5. vSphere/ESXI 6.0 服务器U盘安装详细教程
  6. 吕本富:中国Web2.0缺乏创新 与美国差距拉大
  7. Charles抓包工具安装教程
  8. 亚商投顾早餐FM/0905北交所首只指数、两融制度要来了
  9. Linux进程调用execve,关于linux:在C语言中使用execve loader时子进程如何终止
  10. ADF单位根检验方法