第一部分views 简介
A view is a unit of your app that knows how to draw itself.// app 的基本单位,知道怎么绘制自己.
view能够感觉到用户点击它.能够被点击的表现.
- Chapter 1
讨论views的最主要的方面 - 他们的层级,可见性,position,和自动布局(autolayout) - Chapter 2
描述drawing.一个view知道怎么draw(绘制)它自己.告诉view ,想要他draw成什么样子.
From displaying an existingimage to constructing a drawing in code - Chapter 3
解释layer. The drawing power of a view comes ultimately from its layer.To put it another way, a layer is effectively the aspect of a view thatknows how to draw — with even more power. - Chapter 4
讲解Animation. 动画的力量起源于layer. - Chapter 5
讲解touches. iOS 基于view 对于touches的感觉和反应.
with details on howtouches
are routed to the appropriate view and how you can customize that routing.
第一章
views 简介
a view 知道怎么绘制自己在一个矩形区域中.你的app能够看见归结于views.
view很简单就能被创建.
view 也是responder(UIView is a subclass of UIResponder).view是服从于用户交互.such as: taps and swipes.因此,view基于的是用户看到的和用户touches的.
window
view最顶级的层级是app的window.是UIWindow的实例.UIWindow是UIView的一个子类.你的app应该只有一个主要的window.在启动的时候,被创建,不会被销毁也不会被替换.
最底层的bacground,也是最基础的superview.
app最终是由构成的.UIApplicationMain
.
- UIApplicationMain 实例化
UIApplication
并且持有这个实例.所以能够使用UIApplication.shared
去访问.It retains the app delegate instance and assigns it as the application instance’sdelegate. - UIApplecationMain looks to see whether your app uses a main storyboard, by looking at the
Info.plist
keyMain storyboard file base name
,一般不要去修改main.storyboard. - 如果你的app使用一个main storyboard.UIApplicationMain实例化UIWindow并且分配这个window实例给app delegate的window属性.这个window属性持有它.
- when a view controller becomes the main window's rootViewController, its main view(its view ) is made the one and only immediate subview of your main window--
the main window’s root view.
all other views in your main window will be subviews of the root view.
thus, the root view is the highest object in the view hierarchy that the user will catch a glimpse of the window.
In generalyou’ll have no reason to change anything else about the window itself. - UIApplicationMain calls the app delegate’s application(_:didFinishLaunching-WithOptions:).
- 你的app的界面不能被看见,除非window包含了它.is made the app's key window.因此,if your app uses a main storyboard, UIApplication-Main calls the window’s instance method
makeKeyAndVisible
.(就调用了makeKeyAndVisible)
subview and superview
- 如果一个view 被移除,他的子view也就全部被移除
- 一个view的alpha 为多少,那么他的子view的alpha也为多少.
- 一个view能够选择性的限制他的子views,因此所有在父view的边界之外的view都不会被显示.叫做clipping, 使用view的
clipsToBounds
属性. - 一个父view拥有他的子views.在内存管理来说,就像一个数组拥有他的所有的元素;他持有他们并且当这个subview被销毁或者从子views中移除的时候.
- 一个view的size被改变,他的subviews会自动改变size.
一个UIView有一个superview的属性和一个subviews的属性(一个UIView objects的数组,从后到前的排列次序).
isDescendant(of:)
让你检测是否一个view是否是另外的view的subview.
移除自己的子views的正确方法:
(!)
myView.subviews.forEach {$0.removeFromSuperview()}
奇怪的是没有一个一次性移除完的方法.
Visibility and Opacity
a view can be made invisible by setting its isHidden
property to true
. hidding a view takes it(and its subviews) out of the visible interface without the overhead of actually removing it from the view hierarchy.
a view can be assigned a background color throught its background Color
property. a color is a UIColor. a view whose background color is nil
(the default) is clear color.
a view's alpha
property. 1.0 means opaqye, 0.0 means transparent. 如果一个view 是完全透明的,或者接近于透明(< 0.01).是不能被交互的, 不能被touche.
a view 的 alpha
属性会影响他的backgroundColor和他的内容。a view 的 alpha
为0.5, 那么它的子类的alpha 也是相应的0.5.
(Just to make matters more complicated, colors have an alpha value as well. So, forexample, a view can have an alpha of 1.0 but still have a transparent backgroundbecause its backgroundColor has an alpha less than 1.0.)
Frame Bounds Center
略 因为大家都懂
Window Coordinates and Screen Coordinates window的坐标和Screen的坐标
The device的屏幕没有frame,但是他有bounds. the main window没有superview,但是他的frame is set with respect to the screen's bounds.
let w = UIWindow(frame: UIScreen.main.bounds)
在iOS9之后可以这样写:
let w = UIWindow()
在iOS7 和iOS7之前,screent's 坐标是不变的.iOS8 介绍了一个主要的改变:当app旋转去补偿device的旋转,the screen(and with it, the window) is waht rotates.因此这里有一个变换.Thus there is a transposition of the size components of the screen’s bounds, and a corresponding transposition of the size components of windows's bounds.
The screen therefore reports its coordinates through 2 different properties; their values are typed as UICoordinateSpace
, a protocal(also adopted by UIView) that provides a bounds
property:
这里有一篇关于Coordinate的文章:http://www.tuicool.com/articles/EvmEn2B
UIScreen 在 iOS8中也是旋转的.
有几点改变:
[UIScreen bounds] -> interface-oriented
[UIScreen applicationFrame] -> interface-oriented
status bar frame notifications -> interface-oriented
-
keyboard frame notificaitons -> interface-oriented ()
在iOS8中苹果认为设备的旋转就是当前Screen的Bounds噶生了变化.因此引入了Size Class的概念.苹果使用UITraitCollection来描述不同的Size Class的信息.当UITraitColelction发生了改变(设备发生了旋转)- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
就会被调用.首先被处罚的是UIScreen
,然后逐级向下传递:UIScreen -> UIWindow -> UIViewController -> ChildViewControllers -> View -> Subviews
UICoordinateSpace protocol
iOS 8之前,window和screen的坐标是不变的。它们不会进行旋转,而是一直处于竖屏时的坐标体系。我们是在View Controller旋转方法中做对应的处理。
iOS8之后,像上文描述的那样,window和screen的坐标会随着app旋转而改变。 也不总是这样的情况,因为View Controller仍然决定了App在当前视图下支持哪几个方向。 在某些时候,我们需要一些修正之后的frame(比如需要存储touch事件在屏幕上的坐标),这个时候新的 UICoordinateSpace 就登场了。
在iOS 8上UIScreen提供了两个新的属性:
@property (readonly) id coordinateSpace NS_AVAILABLE_IOS(8_0);
@property (readonly) id fixedCoordinateSpace NS_AVAILABLE_IOS(8_0);
(?) 1. UIScreen's coordinateSpace property
this coordinate space rorates.
(?) 2.UIScreen's fixedCoordinateSpace property
this coordinate space is invariant.(不变的)
(?)
To help you convert between coordinate space.UICoordinateSpace
提供了2个方法:
- convert(_:from:)
- convert(_:to:)
第一个参数不是CGPoint就是CGRect.第二个参数是UICoordinateSpace
, 可能是一个UIView或者是UIScreen.
So,for example, suppose we have a UIView v in our interface, and we wish to learn its position in fixed device coordinates. we could do it like this:
let screen = UIScreen.main.fixedCoordinateSpace
let r = v.superview!.convert(v.frame, to: screen)
Transform
一个view的transform
属性改变这个这个view怎么绘制(drawn).
它可以在不影响bounds和center的情况下改变view的表面的size和方向.
一个transformed
的button还是可以正常使用,可以被点击在他的transformed的情况下.
transform 的值是CGAffineTransform
,CGAffineTransform
是一个struct.
CGAffineTransform简介:
transform一般称之为形变属性,其本质是通过矩形变化改变控件的大小,位置,角度等.
self.imageView.transform = CGAffineTransformRotate(self.imageView.transform, M_PI_4); // 旋转
self.imageView.transform = CGAffineTransformScale(self.imageView.transform, 0.9, 0.9); // 缩放
_imageView.transform = CGAffineTransformTranslate(self.imageView.transform, 0, 10); // 移动
上面这三个方法是在transform的基础上进行改变的.
CGAffineTransformMakeRotation(旋转)、CGAffineTransformMakeScale(缩放)、CGAffineTransformMakeTranslation(移动);
这三个方法是直接设置的.
/* transfrom */
let v1 = UIView.init(frame:CGRect(113, 111, 132, 194))
v1.backgroundColor = UIColor(red: 1, green: 0.4, blue: 1, alpha: 1)
let v2 = UIView(frame:v1.bounds.insetBy(dx: 10, dy: 10)) // 可以insetBy来设置相对的frame
v2.backgroundColor = UIColor(red: 0.5, green: 1, blue: 0, alpha: 1)
self.view.addSubview(v1)
v1.addSubview(v2)
v1.transform = CGAffineTransform(rotationAngle:45 * .pi/180) // 旋转45°
print(v1.frame) // 可以注意就是transform的旋转,它的frame 改变了:(63.7415946665928, 92.7415946665928, 230.516810666815, 230.516810666815)
//注意: 这个旋转,
- center并没有改变
- view的bounds也没有改变
- 内部的坐标系统也没有改变,所以它的子view还是相对于它位置没有变的.
所以如果view的transform不是它的identity transform(初始的时候的transform,没有任何改变过的)的时候,你不能设置它的frame.
以后旋转多少度可以这样写:
45 * .pi/180 // 45°
Trait Collections and Size Classes
traitCollection
UITraitEnvironment protocol
参考王巍的博客:https://onevcat.com/2014/07/ios-ui-unique/
iOS8强调多次的主题就是大统一,包括界面的大统一.
iOS 8 SDK 最重要的关键字就是自适应.iOS8着重解决的就是apple设备开始碎片化的这个问题,6 , 6plus等等.
Size Classes
iOS8在界面设计迎来一个革命性的变化-Size Classes.
iOS6引入了AutoLayout,来帮助开发者使用约束进行布局,这样使得在某些情况下我们不在需要考虑尺寸.而可以使用约束来规定位置.
(!)
注意:Universal指的是iphone 和ipad 通用.
AutoLayout只是一个更具约束来进行布局的方案. 而在对应不同设备的具体情况下的体验上还有欠缺。一个最明显的问题是它不能根据设备类型来确定不同的交互体验。很多时候你还是需要判断设备到底是 iPhone 还是 iPad,以及现在的设备方向究竟是竖直还是水平来做出判断。这样的话我们还是难以彻底摆脱对于设备的判断和依赖,而之后如果有新的尺寸和设备出现的话,这种依赖关系显然显得十分脆弱的(想想要是有 iWatch 的话..) -- 摘抄自王巍博客.
所以在 iOS 8 里,Apple 从最初的设计哲学上将原来的方式推翻了,并引入了一整套新的理念,来适应设备不断的发展。这就是 Size Classes
。
不再根据设备屏幕的具体尺寸来进行区分,而是通过它们的感官表现,将其分为普通 (Regular) 和紧密 (Compact) 两个种类 (class)。开发者便可以无视具体的尺寸,而是对这这两类和它们的组合进行适配。这样不论在设计时还是代码上,我们都可以不再受限于具体的尺寸,而是变成遵循尺寸的视觉感官来进行适配。
简单来说,现在的 iPad 不论横屏还是竖屏,两个方向均是 Regular 的;
而对于 iPhone,竖屏时竖直方向为 Regular,水平方向是 Compact,而在横屏时两个方向都是 Compact。
要注意的是,这里和谈到的设备和方向,都仅仅只是为了给大家一个直观的印象。相信随着设备的变化,这个分类也会发生变动和更新。Size Classes 的设计哲学就是尺寸无关,在实际中我们也应该尽量把具体的尺寸抛开脑后,而去尽快习惯和适应新的体系。
** UITraitCollection 和 UITraitEnvironment**
为了表征 Size Classes,Apple 在 iOS 8 中引入了一个新的类,UITraitCollection。
这个类封装了像水平和竖直方向的 Size Class 等信息.
iOS 8 的 UIKit 中大多数 UI 的基础类 (包括 UIScreen,UIWindow,UIViewController 和 UIView) 都实现了 UITraitEnvironment 这个接口,通过其中的 traitCollection 这个属性,我们可以拿到对应的 UITraitCollection 对象,从而得知当前的 Size Class,并进一步确定界面的布局。
和 UIKit 中的响应者链正好相反,traitCollection 将会在 view hierarchy 中自上而下地进行传递。对于没有指定 traitCollection 的 UI 部件,将使用其父节点的 traitCollection。这在布局包含 childViewController 的界面的时候会相当有用。在 UITraitEnvironment 这个接口中另一个非常有用的是 -traitCollectionDidChange:。在 traitCollection 发生变化时,这个方法将被调用。在实际操作时,我们往往会在 ViewController 中重写 -traitCollectionDidChange: 或者 -willTransitionToTraitCollection:withTransitionCoordinator: 方法 (对于 ViewController 来说的话,后者也许是更好的选择,因为提供了转场上下文方便进行动画;但是对于普通的 View 来说就只有前面一个方法了),然后在其中对当前的 traitCollection 进行判断,并进行重新布局以及动画。代码看起来大概会是这个样子:
- (void)willTransitionToTraitCollection:(UITraitCollection *)newCollection
withTransitionCoordinator:(id )coordinator
{
[super willTransitionToTraitCollection:newCollection
withTransitionCoordinator:coordinator];
[coordinator animateAlongsideTransition:^(id context) {
if (newCollection.verticalSizeClass == UIUserInterfaceSizeClassCompact) {
//To Do: modify something for compact vertical size
} else {
//To Do: modify something for other vertical size
}
[self.view setNeedsLayout];
} completion:nil];
}
在两个 To Do 中,我们应该删除或者添加或者更改不同条件下的 Auto Layout 约束 (当然,你也可以干其他任何你想做的事情),然后调用 -setNeedsLayout 来在上下文中触发转移动画。如果你坚持用代码来处理的话,可能需要面临对于不同 Size Classes 来做移除旧的约束和添加新的约束这样的事情,可以说是很麻烦 (至少我觉得是麻烦的要死)。
但是如果我们使用 IB 的话,这些事情和代码都可以省掉,我们可以非常方便地在 IB 中指定各种 Size Classes 的约束 (稍后会介绍如何使用 IB 来对应 Size Classes)。另外使用 IB 不仅可以节约成百上千行的布局代码,更可以从新的 Xcode 和 IB 中得到很多设计时就可以实时监视,查看并且调试的特性。可以说手写 UI 和使用 IB 设计的时间消耗和成本差距被进一步拉大,并且出现了很多手写 UI 无法实现,但是 IB 可以不假思索地完成的任务。从这个意义上来说,新的 IB 和 Size Classes 系统可以说无情地给手写代码判了个死缓。
另外,新的 API 和体系的引入也同时给很多我们熟悉的 UIViewController 的有关旋转的老朋友判了死刑,比如下面这些 API 都弃用了:
@property(nonatomic, readonly) UIInterfaceOrientation interfaceOrientation
- willRotateToInterfaceOrientation:duration:
- willAnimateRotationToInterfaceOrientation:duration:
- didRotateFromInterfaceOrientation:
- shouldAutomaticallyForwardRotationMethods
现在全部统一到了 viewWillTransitionToSize:withTransitionCoordinator:
,旋转的概念不再被提倡使用。其实仔细想想,所谓旋转,不过就是一种 Size 的改变而已
在 Interface Builder 中使用 Size Classes
IB的画布是正方形,这是Size Classes
对应的编辑方式.
IB 界面的正下方,你可以看到一个 wAny hAny 的按钮 ,
代表现在的 IB 是对应任意高度和任意宽度的。
这样我们就很容易在同一个 storyboard 文件里对不同的设备进行适配:按照设备需要添加或者编辑某些约束,或者是在特定尺寸下隐藏某些 view (使用 Attribute Inspector 里的 Installed 选框的加号添加)。这使得使用 IB 制作通用程序变简单了,我们不再需要为 iPhone 和 iPad 准备两套 storyboard 了。
Size Classes 和 Image Asset 及 UIAppearence
Image Asset 里也加入了对 Size Classes 的支持,也就是说,我们可以对不同的 Size Class 指定不同的图片了。
在 Image Asset 的编辑面板中选择某张图片,Inspector 里现在多了一个 Width 和 Height 的组合.
alpha hidden opaque的区别
http://blog.csdn.net/wzzvictory/article/details/10076323
opaque属性:
该属性用于决定改接受者(UIView的实例)是否然其视图不透明.
用处:
- 给绘图系统提供一个性能优化开关.如果该值为YES,那么绘图在绘制改视图的时候吧整个视图当做不透明(相当于alpha=1)对待。这样,绘图系统在执行绘图过程中会优化一些操作并且提升系统性能;如果是NO,那么绘图系统将其和其他内容平等对待,不去做优化操作。为了性能方面的考量,默认被设置为YES(也就是优化).
- 这个消息和alpha是有关系的.
一个不透明的视图需要整个边界的内容都是不透明的.基于这个原因.opaque设置为YES.要求对应的alpha必须为1.0,如果一个UIView实例opaque被设置为YES,而同事它又没有完全填充它的边界(bounds),或者它包好了整个或者部分的透明的内容视图,那么将会导致未知的结果.
所以,如果视图部分或者全部支持透明,那么你必须把opaque这个值设置为NO.
**draw **
draw 就是 oc中的 drawRect.参考:http://blog.csdn.net/liliangchw/article/details/8448381
oc中 -(void)drawRect:(CGRect)aRect;
,
它的参数是一个rect,也就是一个矩形,是我们需要重绘的区域.可以忽略参数,它只是为了性能优化,只在固定的区域绘图.
注意:不能主动去调用drawRect,因为它是由系统去调用的.
我们告诉系统需要重绘的话,需要发送2个消息:
1. (void)setNeedsDisplay;
2. (void)setNeedsDisplayInRect:(CGRect)aRect;
可以认为初始化的时候设置的是一个点,然后晚些系统查看所有需要重绘的东西.
然后把它们按照顺序排列,因为有些东西可能会重叠,然后再非常高效的吧需要的东西绘制出来.
这样做有2个好处:
- 让系统依据层的情况最优化性能.
- 如果你的property有一些setter,当你设置的时候需要重绘,这种情况也优化了。
所以你所有的setter都会调用self的setNeedsDisplay来重绘.
如果有人用了你的view,然后调用了好几个这样的setter,只需要重绘一次.每隔=个setNeedsDisplay都会被一起传递过去,然后一次性画出来.而不是每次都重绘.
环境,上下问(context
)决定了你在哪儿绘图,所以你创建环境的方法决定了要在哪儿绘图.
关于这个环境要注意的有一点,每次调用drawRect
环境都是不一样的,所以不要把它保存起来,而是每次都去获取新的.
CGContextRef context = UIGraphicsGetCurrentContext();
这个几乎也是每个drawRect
的第一行.
CGContextRef
是一个coockie
,上下文,不是一个对象。当你有了上下文(环境,context),就可以用来创建轨迹了.