我们如何为iOS动态更改布局

什么是动态更改的布局?

由于设备尺寸的不同,此布局也有所不同。 我上一个项目时遇到了此类问题。 主要思想是缩放屏幕上我们拥有的所有内容。 因为更大的屏幕需要更多的调整。

如何解决?

您可能会得到的第一个想法是“尺寸等级”。 实际上,我们最初也使用了此方法,但这还不够。 当项目增长时,控制器也将增长。 这导致对每个尺寸类别使用更多不同的约束。 当您尝试更改任何内容时,这会使您的生活如地狱般艰难。 这些并不是我们使用此想法遇到的所有问题。 有些控制器的UI非常复杂,几乎与任何设备都不一样,因此我们被迫使用几乎所有可能的尺寸等级。 想象一下,当您需要为iPad进行某些更改时,可能会浪费大量时间来寻找iPad的位置。 当我们不想更改约束常数,而是更改其乘数或优先级时,就会出现一个更大的问题。 在size类中实际上是不可能的。 相反,您需要为每个尺寸类别创建不同的约束,并在其中包含所需的值,否则,将对该约束的其他尺寸类别禁用该约束。

我们到底会得到什么?

最终,这种方法真的很难支持,它不灵活,情节提要变得非常复杂,我们有很多难以处理的约束。 最后,无法区分4s,5、6等设备(将来列表可能会更大)。

新分辨率的设备呢?

我们需要为这些创建新的UI。 这意味着我们可能需要在新设备发布时更新代码并将新版本上传到Appstore。 这可能不是项目的最佳解决方案,因为此更新可能由不熟悉先前项目体系结构的其他开发人员执行。 我可以发现的唯一好处是,所有UI工作人员均在UI文件中执行,并且未与代码连接。 这使我们的代码更加清晰。
即使有很多弊端,我们也可以接受这种方法,因为当我们打开它时,它有可能缩放所有设备的UI,这在Size Classs中是不可能的。
我们可以得出的结论是忽略尺寸等级。

下一步是什么?

我们要做的下一步是从代码更改约束值。 由于设备的差异,我们使用不同的值。 这种方法有多好? 实际上,这可能很奇怪,但是绝对比Size类好。 我们可以轻松地将此代码与其余所有代码分开。 是的,这不是明确的分离,并且代码量大量增加。

最后,代码量急剧增加。 添加新视图或更改现有视图需要花费大量时间。 但是,对于开发人员来说,情况变得更加清晰,并且情节提要文件的权重降低了。 这种方法使我们能够覆盖所有设备。 未来设备的问题仍然存在,没有人愿意回到他们以前的项目并为所有视图添加新值。 对于开发人员来说,这将是一个地狱,特别是如果该项目很大。

我想强调的是,以上所有内容都可以应用于字体以及约束。
接下来的两个解决方案可能是最好的,各有其优缺点。 最后,您只需要决定哪个更适合您即可。

先前方法的主要问题是什么?

我们需要将大量代码添加到新视图中。 这会花费很多时间,并且代码变得非常复杂和丑陋。 问题是在哪里删除此代码,或者对我们来说如何简化呢? 在IBInspectable属性中找到了该解决方案。 我们为每个更改约束常量的设备添加了约束属性。 这是一个代码示例,该代码是如何实现的。

  @interface NSLayoutConstraint(BBBDevices) @属性(非原子,分配)IBInspectable CGFloat iPhone4Constants; 
  @属性(非原子的,分配的)IBInspectable CGFloat iPhone5常量; 
  @属性(非原子,分配)IBInspectable CGFloat iPhone6常量; 
  @属性(非原子,分配)IBInspectable CGFloat iPhone6PlusConstants; 
  @属性(非原子,分配)IBInspectable CGFloat iPadConstants; 
  @属性(非原子的,分配的)IBInspectable CGFloat iPadProConstants;  @end @implementation NSLayoutConstraint(BBBDevices) #pragma mark- 属性 -(void)setIPhone4Constants:(CGFloat)iPhone4Constants { 
      objc_setAssociatedObject(self,@selector(iPhone4Constants),@(iPhone4Constants),OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
      如果([DeviceOperations deviceModel] == DeviceModelIPhone4){ 
          self.constant = iPhone4Constants; 
      } 
  } -(void)setIPhone5Constants:(CGFloat)iPhone5Constants { 
      objc_setAssociatedObject(self,@selector(iPhone5Constants),@(iPhone5Constants),OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
      如果([DeviceOperations deviceModel] == DeviceModelIPhone5){ 
          self.constant = iPhone5常量; 
      } 
  } -(void)setIPhone6Constants:(CGFloat)iPhone6Constants { 
      objc_setAssociatedObject(self,@selector(iPhone6Constants),@(iPhone6Constants),OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
      如果([[DeviceOperations deviceModel] == DeviceModelIPhone6){ 
          self.constant = iPhone6常量; 
      } 
  } -(void)setIPhone6PlusConstants:(CGFloat)iPhone6PlusConstants { 
      objc_setAssociatedObject(self,@selector(iPhone6PlusConstants),@(iPhone6PlusConstants),OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
      如果([DeviceOperations deviceModel] == DeviceModelIPhone6plus){ 
          self.constant = iPhone6PlusConstants; 
      } 
  } -(void)setIPadConstants:(CGFloat)iPadConstants { 
      objc_setAssociatedObject(self,@selector(iPadConstants),@(iPadConstants),OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
      如果([DeviceOperations deviceModel] == DeviceModelIPad){ 
          self.constant = iPadConstants; 
      } 
  } -(void)setIPadProConstants:(CGFloat)iPadProConstants { 
      objc_setAssociatedObject(self,@selector(iPadProConstants),@(iPadProConstants),OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
      如果([DeviceOperations deviceModel] == DeviceModelIPadPro){ 
          self.constant = iPadProConstants; 
      } 
  } -(CGFloat)iPhone4Constants { 
      返回[objc_getAssociatedObject(self,@selector(iPhone4Constants))floatValue]; 
  } -(CGFloat)iPhone5常量{ 
      返回[objc_getAssociatedObject(self,@selector(iPhone5Constants))floatValue]; 
  } -(CGFloat)iPhone6常量{ 
      返回[objc_getAssociatedObject(self,@selector(iPhone6Constants))floatValue]; 
  } -(CGFloat)iPhone6PlusConstants { 
      返回[objc_getAssociatedObject(self,@selector(iPhone6PlusConstants))floatValue]; 
  } -(CGFloat)iPadConstants { 
      返回[objc_getAssociatedObject(self,@selector(iPadConstants))floatValue]; 
  } -(CGFloat)iPadProConstants { 
      返回[objc_getAssociatedObject(self,@selector(iPadProConstants))floatValue]; 
  } 
  @结束 

如您所见,它的实现非常简单,而且由于它的存在,我们摆脱了痛苦。 我们将所有UI布局构建代码删除到UI文件中。 到目前为止,我们只需要为每个设备输入特定约束的必要值即可。 这样可以节省大量时间。 我们非常高兴如此简单的解决方案能够解决如此复杂的问题。 为更改标签和按钮字体编写了相同的代码。 我认为这部分不需要描述。 虽然,我还不够满意。 那我们应该如何处理新设备? 每次新设备问世时,赠送需要重新实现的产品是否合适? 对于我来说,这只是一个很好的解决方案,但不是一个完美的解决方案。

我开始寻找其他使我完全满意的解决方案。

终于,我找到了银弹。 它是可动态更改的布局。 我给它起了个名字,所以如果您一开始不了解它就不必担心。 这种方法的主要思想是“一切都应按比例执行”。 只需设置成比例的高度,然后将您的高度连接到另一个高度,在大多数情况下,它就是控制器的高度,因为它是静态值。 让我们检查一下,看看现在如何表示所有约束。 我会不时地为您提供一些使用这种方法学到的有用技巧。

第一个技巧是“如果您曾经使用的约束常数不等于0,则可能您做错了事”。 可能不是比例,而是一个静态值,应该更改此常数,以便其他屏幕分辨率完成比例。

让我们回到我们的约束条件。
长宽比-转换为长宽比,因为实际上这是一个比例。
高度和宽度-您可以将高度设置为与对象的其他高度相同,并根据需要将乘数设置为适合。 通常,它是屏幕的高度或宽度。 当设备尺寸更改时,高度和宽度的尺寸也将更改。
另一个技巧是尝试将高度或宽度连接到一些很少更改的对象上。 例如,对于某些父视图,屏幕是不错的选择。

中心Y或中心X-如果对象不完全位于中心,则可以使用乘法器。 然后它将在更大的屏幕中从中心进一步移动。 很简单,不是吗? 这还不是全部。 那领先和落后的空间呢? 我们如何使它们成比例? 约束乘数对我们没有帮助,因此我们找到了另一个解决方案。 您需要创建一个高度为零,比例宽度为零的视图,并将其垂直放置在某处,以避免发出警告。 接下来,您需要将此视图与需要在其之间保持空间的视图(前向常数和尾随常数等于零)之间保持连接,左右一对一。 结果,宽度视图将随着更大的设备尺寸而增加。 连接到该视图时,连接的视图之间的空间也会增加。 这正是我们在寻找的东西。 底部和顶部空间与前导和尾随空间相似,主要区别在于我们将具有零宽度,成比例的高度和水平放置。 将在帮助视图的顶部和底部建立连接。
还有另一件事有用。 构建这样的UI将在前面描述的帮助视图中引起很多问题。 要构建此结构,请将它们放置在其他视图中,您可以在其中轻松找到它们,并为它们指定通用名称以了解与之关联的内容。
嗯,这很难,但我们做到了。

对于缩放字体,我们可以使用最小字体缩放组件,等等。只需要以适当的方式进行配置即可。

最后,我们得到的是,如果设备屏幕尺寸发生变化,我们的布局将动态更新。 字体也将自动更新。 描述了一些简单的案例,您可能会遇到更复杂的案例,但是如果您对该主题有一个大致的了解,则可以轻松解决它们。 如您所见,上述方法中的所有问题均已解决。

更复杂并不意味着会更好。 除非我们遇到新的问题,否则这将是最好的解决方案。 第一个也是最主要的是糟糕的应用程序性能。 众所周知,系统使用线性方程组重新计算所有约束。 它拥有的变量越多,解决起来就越困难。 有了所有这些其他视图,我们使系统很难计算所需的布局。 如果用户界面真的很复杂,它会在低级设备上造成性能问题,甚至可能导致崩溃,因为使用常量总是比使用动态值容易。

发生的第二个问题是复杂的UI文件。 这导致新开发人员从一开始就无法构建良好且灵活的UI的事实。 我不是在说它的大小,因为我们使用了其他视图,所以它们变得更大。

所有这些问题可能对您的项目无济于事,或者对您而言确实是地狱,具体取决于项目的类型及其适合性。

最后,我只想说,我不是在等待获得有史以来最佳解决方案的奖牌,而是分享我们如何解决这些问题。 对于我来说,最后两种解决方案是最好的推荐,并且各有利弊。 您所需要做的就是选择最适合您的项目及其要求的最佳方法。