背景介绍
用frame还是加限制?
经过近4年的争论,采用AutoLayout,通过加限制来确定页面位置,这种观点已经占主流地位。frame还是会用,不过场合已经越来越少。用IB还是纯代码写界面?iOS界传统的争论。纯代码写界面还是有很多人在坚持,特别是除了这些好用的第三方库。本人的观点和这篇文章一致iOS界面布局,代码还是IB?总有一款适合你这篇文章一致,采用混合模式,故事版、xib、代码哪个方便用哪个。这里关于ScrollView的处理值得提倡。
为了代码加限制,苹果还推出了VFL这种“象形文字”,苹果的这种创新精神是值得推崇的,不过是不是东西方文化差异,这种苹果自嗨的东西,怎么让人理解起来这么费劲?有这些功夫,不如思考将加限制那几个API改得好用一点。
用IB加限制比较简单,可是用代码加限制,苹果提供的API实在太难用,跟KVO的API属于同一难用的级别。真不知道苹果是怎么想的,难道不考虑“开发体验”了?
幸亏还有强大的开源社区,在苹果的API上包了一层,让“开发体验”上升了几个层次。
Object-C:Masonry
Swift:SnapKit
这个已经成为事实上的标准了,朋友圈中基本都用这两个中的一个这次介绍的Neon也是代码加限制的,star数量虽然少一点,不过也达到3600了。思路完全不同,“开发体验”也是非常不错的
限制与特色
- 支持iOS9以上? 这个没有确认,只是从它的简介上推测。应该不是这样,因为看其源代码,并没有用到iOS9以上适用的API。
platform :ios, '9.0'
use_frameworks!
pod 'Neon'
代码是用Swfit写的,从介绍说,支持Swift3。现在XCode8默认都不支持iOS7了。所以,对于最低要求支持iOS7以及以下的APP,用这个估计会有困难,也不建议使用。所以,最低支持版本iOS8以上的可以尝试用这个。
从它的源代码看,并没有用AutoLayout那些难用的API,而是直接使用最原始的frame方式。从这一点推测出:不要在View的init或者ViewController的ViewDidLoad中用这个库。那个时候的frame是不可相信的,谁知道会出现什么事情?在View的layoutSubviews或者ViewController的viewWillLayoutSubviews和viewDidLayoutSubviews这两个函数中,frame应该确定了,在这里写应该好一点。
基本的思路是采用“场景法”或者是“案例法”,将常用的“场景”包装成方便使用的API函数。这种思路是非常巧妙的,让使用者在使用的时候不必关心是frame还AutoLayout,按照“人”的思维去思考就可以了。并且,这种语言跟“UI”等“文艺青年”是有共同语言的,没有“iOS技术”的痕迹。Masonry的make,其AutoLayout API的痕迹还是非常明显的,特别是右边距,下边距要用负数这一点,跟原生API一样难用,更不用说跟跟“UI”等“文艺青年”交流了。
从思路上来说,是“相对布局”思想,可是底层却是用绝对布局的frame来实现的。能将这两种看似矛盾的思想结合起来,作者的思路还是非常值得称道的。本来我看了其功能介绍,对其使用方式很认同,马上想到的是借用这种“场景法”思想,用Object-C也写一份,用在当前的工程中。API定义参考它的,具体实现还是用Masonry的make。看了源码之后,才知道自己的想法“脱裤子放屁---多此一举”。
iOS和MacX都能用,是最基础的frame实现
#if os(iOS)
import UIKit
#else
import Cocoa
#endif
功能简介
NeonAnchorable.swift表示单个view在父view中的位置
NeonAlignable.swift表示同级两个view之间位置关系
NeonGroupable.swift表示相同子view的均衡分布
Anchoring Views
居中
public func anchorInCenter(width: CGFloat, height: CGFloat)
填充
public func fillSuperview(left: CGFloat = 0, right: CGFloat = 0, top: CGFloat = 0, bottom: CGFloat = 0)
这里是padding的概念,都是正数。利用Swift默认参数特性,不给参数,就是完全充满。
- 靠近角落
public func anchorInCorner(_ corner: Corner, xPad: CGFloat, yPad: CGFloat, width: CGFloat, height: CGFloat)
// Corner定义
public enum Corner {
case topLeft
case topRight
case bottomLeft
case bottomRight
}
同样这里的xPad、yPad都是正数
- 靠近边
public func anchorToEdge(_ edge: Edge, padding: CGFloat, width: CGFloat, height: CGFloat)
// Edge定义
public enum Edge {
case top
case left
case bottom
case right
}
同样这里的padding是正数
- 靠近边并且填充
public func anchorAndFillEdge(_ edge: Edge, xPad: CGFloat, yPad: CGFloat, otherSize: CGFloat)
otherSize是指填充时需要固定一边,width或者height。比如靠近上边top,那么就要指定高度height,宽度充满父view;这时otherSize就代表高度height
类型是CGFloat,不是CGSize,这个名字有可能带来误解,可以考虑取个更好一点的名字,比如widthOrHeight。
Align
- 与某个固定view的相对位置
public func align(_ align: Align, relativeTo sibling: Frameable, padding: CGFloat, width: CGFloat, height: CGFloat, offset: CGFloat = 0)
// Align定义
public enum Align {
case toTheRightMatchingTop
case toTheRightMatchingBottom
case toTheRightCentered
case toTheLeftMatchingTop
case toTheLeftMatchingBottom
case toTheLeftCentered
case underMatchingLeft
case underMatchingRight
case underCentered
case aboveMatchingLeft
case aboveMatchingRight
case aboveCentered
}
- Frameable是自定义的协议,UIView和CALayer在NeonExtensions.swift已经遵循这个协议,可以正常使用
- offset默认是0,在源码中是+,正负将带来不同的偏移方向
- 与某个固定view的相对位置,并且填充共同的父view
public func alignAndFill(align: Align, relativeTo sibling: Frameable, padding: CGFloat, offset: CGFloat = 0)
public func alignAndFillHeight(align: Align, relativeTo sibling: Frameable, padding: CGFloat, width: CGFloat, offset: CGFloat = 0)
public func alignAndFillWidth(align: Align, relativeTo sibling: Frameable, padding: CGFloat, height: CGFloat, offset: CGFloat = 0)
- 与两个view之间的相对位置,其中一个为主要参考
public func alignBetweenHorizontal(align: Align, primaryView: Frameable, secondaryView: Frameable, padding: CGFloat, height: CGFloat, offset: CGFloat = 0)
public func alignBetweenVertical(align: Align, primaryView: Frameable, secondaryView: Frameable, padding: CGFloat, width: CGFloat, offset: CGFloat = 0)
Align是相对于primaryView来定的
Grouping
- 居中均匀分布
public func groupInCenter(group: Group, views: [Frameable], padding: CGFloat, width: CGFloat, height: CGFloat)
// Group定义
public enum Group {
case horizontal
case vertical
}
- 靠近角落均匀分布
public func groupInCorner(group: Group, views: [Frameable], inCorner corner: Corner, padding: CGFloat, width: CGFloat, height: CGFloat)
- 靠近边均匀分布
public func groupAgainstEdge(group: Group, views: [Frameable], againstEdge edge: Edge, padding: CGFloat, width: CGFloat, height: CGFloat)
- 相对于某个固定view的均匀分布
public func groupAndAlign(group: Group, andAlign align: Align, views: [Frameable], relativeTo sibling: Frameable, padding: CGFloat, width: CGFloat, height: CGFloat)
- 填充满并且均匀分布
public func groupAndFill(group: Group, views: [Frameable], padding: CGFloat)
UILabel由内容决定大小
- 引入两个固定变量,表示内容决定大小
// MARK: AutoHeight
//
///
/// `CGFloat` constant used to specify that you want the height to be automatically calculated
/// using `sizeToFit()`.
///
public let AutoHeight : CGFloat = -1
public let AutoWidth : CGFloat = -1
在需要自适应的地方,对应的width或者height参数输入AutoWidth或者AutoHeight
- 就像注释里面说的,使用
sizeToFit()
实现根据内容自适应
// MARK: UIView implementation of the Neon protocols.
//
extension View : Frameable, Anchorable, Alignable, Groupable {
public var superFrame: CGRect {
guard let superview = superview else {
return CGRect.zero
}
return superview.frame
}
public func setDimensionAutomatically() {
#if os(iOS)
self.sizeToFit()
#else
self.autoresizesSubviews = true
self.autoresizingMask = [.viewWidthSizable, .viewHeightSizable]
#endif
}
}
- 检测到AutoWidth或者AutoHeight这两个特殊参数之后,调用相同函数,再算一遍frame。比如:
public func anchorInCenter(width: CGFloat, height: CGFloat) {
let xOrigin : CGFloat = (superFrame.width / 2.0) - (width / 2.0)
let yOrigin : CGFloat = (superFrame.height / 2.0) - (height / 2.0)
frame = CGRect(x: xOrigin, y: yOrigin, width: width, height: height)
if height == AutoHeight {
self.setDimensionAutomatically()
self.anchorInCenter(width: width, height: self.height)
}
if width == AutoWidth {
self.setDimensionAutomatically()
self.anchorInCenter(width: self.width, height: height)
}
}
感悟
“场景化”是一种很值得借鉴的角度,感觉很“人性化”。如果使用技术用语,不论是frame还是AutoLayout,跟“UI”等“文艺青年”交流总感觉说不到一块去。用这些API交流,将会轻松很多
“场景化”的思维,将frame这种毫无意义,繁琐无聊的布局,赋予了实际的意义,感觉很不错。
用frame实现了“相对布局”的思想,想法很不错
避开了难用的AutoLayoutAPI和不是人看的VFL,利用相对简单的frame来实现,思路比较好。