iOS知识小集 第三期
2016-04-08 11:42
615 查看
这一期主要有三个内容:
Tint Color
Build Configurations in Swift
键盘事件
在
默认情况下,一个视图的
与
枚举UIViewTintAdjustmentMode的定义如下:
因此,当
与
这个方法会在视图的
接下来我们通过示例来看看
先来看看最终效果吧(以下都是盗图,请见谅,太懒了):
![](https://www.shinobicontrols.com/media/371241/tint_color_image_1_350x621.jpg)
这个界面包含的元素主要有
在
首先,我们尝试打印默认的
接下来,我们显式设置
当我们点击”
这段代码主要是随机生成一个颜色值,并赋值给
注:有些控件的特定组成部件的tint color由特定的属性控制,例如进度就有2个tint color:一个用于进度条本身,另一个用于背景。
点击”
![](https://www.shinobicontrols.com/media/371246/tint_color_image_2_350x621.jpg)
可以看到,我们在示例中并有没手动去设置
看来
在界面中还有个
当
另外,我们在子视图
所以方框和”
在这个示例中,比较有意思的是还是对图片的处理。对图像的处理比较简单粗暴,对一个像素而言,如果它的
插个题外话,跟主题关系不大。
在色彩理论(
![](http://www.craftsy.com/blog/wp-content/uploads/2013/04/Screen-Shot-2013-04-30-at-12.46.43-PM.png)
一些基础的理论知识可以参考Hues, Tints, Tones and Shades: What’s the Difference?或更专业的一些文章。
如果我们想指定整个
当弹出一个
有些复杂控件,可以有多个
UIView Class Reference
iOS7 Day-by-Day :: Day 6 :: Tint Color
Appearance and Behavior
Tints and shades
Hues, Tints, Tones and Shades: What’s the Difference?
在
在
不过,
其中
注:在WWDC 2014的“Sharing code between iOS
and OS X”一节(session 233)中,Elizabeth Reid将这种方式称为Shimming
遗憾的是,
我们再来看看
如果我们想自定义一些在调试期间使用的编译配置选项,则可以使用-D标识来告诉编译器,具体操作是在
一个简单的条件编译声明如下所示:
当然,
需要注意的是,在
Cross-platform Swift
Shimming in Swift
Swift System Version Checking
Interacting with C APIs
在涉及到表单输入的界面中,我们通常需要监听一些键盘事件,并根据实际需要来执行相应的操作。如,键盘弹起时,要让我们的
这几个通知的
在此,我感兴趣的是键盘事件的调用顺序和如何获取键盘的大小,以适当的调整视图的大小。
从定义的键盘通知的类型可以看到,实际上我们关注的是三个阶段的键盘的事件:显示、隐藏、大小改变。在此我们设定两个
1) 当我们让
2) 在不隐藏键盘的情况下,让
3) 再收起键盘,打印的日志如下:
从上面的日志可以看出,不管是键盘的显示还是隐藏,都会发送大小改变的通知,而且是在
另外还有两点需要注意的是:
如果是在两个大小相同的键盘之间切换,则不会发送任何消息
如果是普通键盘中类似于中英文键盘的切换,只要大小改变了,都会发送一组或多组与上面2)相同流程的消息
了解了事件的调用顺序,我们就可以根据自己的需要来决定在哪个消息处理方法中来执行操作。为此,我们需要获取一些有用的信息。这些信息是封装在通知的
我们可以通过以下方式来获取这些值:
实际上,
最后说下表单。一个表单界面看着比较简单,但交互和UI总是能想出各种方法来让它变得复杂,而且其实里面设计到的细节还是很多的。像我们金融类的
UIWindow Class Reference
通常,
在
我们通过这个方法就可以来自定义行的视图。时间不早,废话就不多说了,直接上代码吧:
我们定义了一个
不过,还有个需要注意的就是,虽然看上去是显示了
UIPickerViewDelegate Protocol Reference
有个小需求,需要遍历当前导航控制器栈的所有
获取到的
编译器会报错,提示如下:
实际上,不管是
在stackoverflow上有这样一个有趣的比方,我犯懒就直接贴出来了:
To understand the difference, let me make a real life example: you buy a new TV on ebay, the package is shipped to you, the first thing you do is to check if the package (the optional) is empty (nil). Once you verify that the TV is inside, you have to unwrap
it, and put the box aside. You cannot use the TV while it’s in the package. Similarly, an optional is a container: it is not the value it contains, and it doesn’t have the same type. It can be empty, or it can contain a valid value.
所以,这里的处理应该是:
Tint Color
Build Configurations in Swift
键盘事件
Tint Color
在iOS 7后,
UIView新增加了一个
tintColor属性,这个属性定义了一个非默认的着色颜色值,其值的设置会影响到以视图为根视图的整个视图层次结构。它主要是应用到诸如app图标、导航栏、按钮等一些控件上,以获取一些有意思的视觉效果。
tintColor属性的声明如下:
1 | var tintColor: UIColor! |
tintColor是为
nil的,这意味着视图将使用父视图的
tint color值。当我们指定了一个视图的
tintColor后,这个色值会自动传播到视图层次结构(以当前视图为根视图)中所有的子视图上。如果系统在视图层次结构中没有找到一个非默认的
tintColor值,则会使用系统定义的颜色值(蓝色,
RGB值为
[0,0.478431,1],我们可以在IB中看到这个颜色)。因此,这个值总是会返回一个颜色值,即我们没有指定它。
与
tintColor属性相关的还有个
tintAdjustmentMode属性,它是一个枚举值,定义了
tint color的调整模式。其声明如下:
1 | var tintAdjustmentMode: UIViewTintAdjustmentMode |
12 | enum UIViewTintAdjustmentMode : Int { case Automatic // 视图的着色调整模式与父视图一致 case Normal // 视图的tintColor属性返回完全未修改的视图着色颜色 case Dimmed // 视图的tintColor属性返回一个去饱和度的、变暗的视图着色颜色 } |
tintAdjustmentMode属性设置为
Dimmed时,
tintColor的颜色值会自动变暗。而如果我们在视图层次结构中没有找到默认值,则该值默认是
Normal。
与
tintColor相关的还有一个
tintColorDidChange方法,其声明如下:
1 | func tintColorDidChange() |
tintColor或
tintAdjustmentMode属性改变时自动调用。另外,如果当前视图的父视图的
tintColor或
tintAdjustmentMode属性改变时,也会调用这个方法。我们可以在这个方法中根据需要去刷新我们的视图。
示例
接下来我们通过示例来看看tintColor的强大功能(示例盗用了
Sam Davies写的一个例子,具体可以查看iOS7 Day-by-Day :: Day 6 :: Tint Color,我就负责搬砖,用swift实现了一下,代码可以在这里下载)。
先来看看最终效果吧(以下都是盗图,请见谅,太懒了):
![](https://www.shinobicontrols.com/media/371241/tint_color_image_1_350x621.jpg)
这个界面包含的元素主要有
UIButton,
UISlider,
UIProgressView,
UIStepper,
UIImageView,
ToolBar和一个自定义的子视图
CustomView。接下来我们便来看看修改视图的
tintColor会对这些控件产生什么样的影响。
在
ViewController的
viewDidLoad方法中,我们做了如下设置:
12 | override func viewDidLoad() { |
tintColor和
tintAdjustmentMode,分别输出了
[UIDeviceRGBColorSpace 0 0.478431 1 1]和
1,这是在我们没有对整个视图层次结构设置任何
tint color相关的值的情况下的输出。可以看到,虽然我们没有设置
tintColor,但它仍然返回了系统的默认值;而
tintAdjustmentMode则默认返回
Normal的原始值。
接下来,我们显式设置
tintAdjustmentMode的值为
Normal,同时设置
UIImageView的图片及渲染模式。
当我们点击”
Change Color“按钮时,会执行以下的事件处理方法:
12 | @IBAction func changeColorHandler(sender: AnyObject) { let hue = CGFloat(arc4random() % 256) / 256.0 let saturation = CGFloat(arc4random() % 128) / 256.0 + 0.5 let brightness = CGFloat(arc4random() % 128) / 256.0 + 0.5 let color = UIColor(hue: hue, saturation: saturation, brightness: brightness, alpha: 1.0) self.view.tintColor = color updateViewConstraints() } private func updateProgressViewTint() { self.progressView?.progressTintColor = self.view.tintColor } |
self.view的
tintColor属性,同时去更新进度条的tintColor值。
注:有些控件的特定组成部件的tint color由特定的属性控制,例如进度就有2个tint color:一个用于进度条本身,另一个用于背景。
点击”
Change Color“按钮,可得到以下效果:
![](https://www.shinobicontrols.com/media/371246/tint_color_image_2_350x621.jpg)
可以看到,我们在示例中并有没手动去设置
UIButton,
UISlider,
UIStepper,
UIImageView,
ToolBar等子视图的颜色值,但随着
self.view的
tintColor属性颜色值的变化,这些控件的外观也同时跟着改变。也就是说
self.view的
tintColor属性颜色值的变化,影响到了以
self.view为根视图的整个视图层次结果中所有子视图的外观。
看来
tintColor还是很强大的嘛。
在界面中还有个
UISwitch,这个是用来开启关闭
dim tint的功能,其对应处理方法如下:
12 | @IBAction func dimTimtHandler(sender: AnyObject) { if let isOn = self.dimTintSwitch?.on { self.view.tintAdjustmentMode = isOn ? .Dimmed : .Normal } updateViewConstraints() } |
tintAdjustmentMode设置
Dimmed时,其实际的效果是整个色值都变暗(此处无图可盗)。
另外,我们在子视图
CustomView中重写了
tintColorDidChange方法,以监听
tintColor的变化,以更新我们的自定义视图,其实现如下:
12 | override func tintColorDidChange() { tintColorLabel.textColor = self.tintColor tintColorBlock.backgroundColor = self.tintColor } |
Tint color label“颜色是跟着子视图的
tintColor来变化的,而子视图的
tintColor又是继承自父视图的。
在这个示例中,比较有意思的是还是对图片的处理。对图像的处理比较简单粗暴,对一个像素而言,如果它的
alpha值为
1的话,就将它的颜色设置为
tint color;如果不为
1的话,则设置为透明的。示例中的忍者头像就是这么处理的。不过我们需要设置图片的
imageWithRenderingMode属性为
AlwaysTemplate,这样渲染图片时会将其渲染为一个模板而忽略它的颜色信息,如代码所示:
12 | var shinobiHead = UIImage(named: "shinobihead") // 设置渲染模式 shinobiHead = shinobiHead?.imageWithRenderingMode(.AlwaysTemplate) |
题外话
插个题外话,跟主题关系不大。在色彩理论(
color theory)中,一个
tint color是一种颜色与白色的混合。与之类似的是
shade color和
tone color。
shade color是将颜色与黑色混合,
tone color是将颜色与灰色混合。它们都是基于
Hues色调的。这几个色值的效果如下图所示:
![](http://www.craftsy.com/blog/wp-content/uploads/2013/04/Screen-Shot-2013-04-30-at-12.46.43-PM.png)
一些基础的理论知识可以参考Hues, Tints, Tones and Shades: What’s the Difference?或更专业的一些文章。
小结
如果我们想指定整个App的
tint color,则可以通过设置
window的
tint color。这样同一个
window下的所有子视图都会继承此
tint color。
当弹出一个
alert或者
action sheet时,
iOS7会自动将后面视图的
tint color变暗。此时,我们可以在自定义视图中重写
tintColorDidChange方法来执行我们想要的操作。
有些复杂控件,可以有多个
tint color,不同的
tint color控件不同的部分。如上面提到的
UIProgressView,又如
navigation bars,
tab bars,
toolbars,
search bars,
scope bars等,这些控件的背景着色颜色可以使用
barTintColor属性来处理。
参考
UIView Class ReferenceiOS7 Day-by-Day :: Day 6 :: Tint Color
Appearance and Behavior
Tints and shades
Hues, Tints, Tones and Shades: What’s the Difference?
Build Configurations in Swift
在Objective-C中,我们经常使用预处理指令来帮助我们根据不同的平台执行不同的代码,以让我们的代码支持不同的平台,如:
12 | #if TARGET_OS_IPHONE #define MAS_VIEW UIView #elif TARGET_OS_MAC #define MAS_VIEW NSView #endif |
swift中,由于对
C语言支持没有
Objective-C来得那么友好(暂时不知
swift 2到
C的支持如何),所以我们无法像在
Objective-C中那样自如而舒坦地使用预处理指令。
不过,
swift也提供了自己的方式来支持条件编译,即使用
build configurations(构建配置)。
Build configurations已经包含了字面量
true和
false,以及两个平台测试函数
os()和
arch()。
其中
os()用于测试系统类型,可传入的参数包含
OSX,
iOS,
watchOS,所以上面的代码在
swift可改成:
12 | #if os(iOS) typealias MAS_VIEW = UIView #elseif os(OSX) typealias MAS_VIEW = NSView #endif |
and OS X”一节(session 233)中,Elizabeth Reid将这种方式称为Shimming
遗憾的是,
os()只能检测系统类型,而无法检测系统的版本,所以这些工作只能放在运行时去处理。关于如何检测系统的版本,
Mattt Thompson老大在它的Swift System Version Checking一文中给了我们答案。
我们再来看看
arch()。
arch()用于测试
CPU的架构,可传入的值包括
x86_64,
arm,
arm64,
i386。需要注意的是
arch(arm)对于
ARM 64的设备来说,不会返回
true。而
arch(i386)在
32位的
iOS模拟器上编译时会返回
true。
如果我们想自定义一些在调试期间使用的编译配置选项,则可以使用-D标识来告诉编译器,具体操作是在
"Build Setting"–>
"Swift Compiler-Custom Flags"–>
"Other Swift Flags"–>
"Debug"中添加所需要的配置选项。如我们想添加常用的
DEGUB选项,则可以在此加上
"-D DEBUG"。这样我们就可以在代码中来执行一些
debug与
release时不同的操作,如
12 | #if DEBUG let totalSeconds = totalMinutes #else let totalSeconds = totalMinutes * 60 #endif |
12 | #if build configuration statements #else statements #endif |
statements中可以包含
0个或多个有效的
swift的
statements,其中可以包括表达式、语句、和控制流语句。另外,我们也可以使用
&&和
||操作符来组合多个
build configuration,同时,可以使用!操作符来对
build configuration取反,如下所示:
12 | #if build configuration && !build configuration statements #elseif build configuration statements #else statements #endif |
swift中,条件编译语句必须在语法上是有效的,因为即使这些代码不会被编译,
swift也会对其进行语法检查。
参考
Cross-platform SwiftShimming in Swift
Swift System Version Checking
Interacting with C APIs
键盘事件
在涉及到表单输入的界面中,我们通常需要监听一些键盘事件,并根据实际需要来执行相应的操作。如,键盘弹起时,要让我们的UIScrollView自动收缩,以能看到整个
UIScrollView的内容。为此,在
UIWindow.h中定义了如下
6个通知常量,来配合键盘在不同时间点的事件处理:
12 | UIKeyboardWillShowNotification // 键盘显示之前 UIKeyboardDidShowNotification // 键盘显示完成后 UIKeyboardWillHideNotification // 键盘隐藏之前 UIKeyboardDidHideNotification // 键盘消息之后 UIKeyboardWillChangeFrameNotification // 键盘大小改变之前 UIKeyboardDidChangeFrameNotification // 键盘大小改变之后 |
object对象都是
nil。而
userInfo字典都包含了一些键盘的信息,主要是键盘的位置大小信息,我们可以通过使用以下的
key来获取字典中对应的值:
12 | // 键盘在动画开始前的frame let UIKeyboardFrameBeginUserInfoKey: String // 键盘在动画线束后的frame let UIKeyboardFrameEndUserInfoKey: String // 键盘的动画曲线 let UIKeyboardAnimationCurveUserInfoKey: String // 键盘的动画时间 let UIKeyboardAnimationDurationUserInfoKey: String |
从定义的键盘通知的类型可以看到,实际上我们关注的是三个阶段的键盘的事件:显示、隐藏、大小改变。在此我们设定两个
UITextField,它们的键盘类型不同:一个是普通键盘,一个是数字键盘。我们监听所有的键盘事件,并打印相关日志(在此就不贴代码了),直接看结果。
1) 当我们让
textField1获取输入焦点时,打印的日志如下:
12 | keyboard will change keyboard will show keyboard did change keyboard did show |
textField2获取焦点,打印的日志如下:
12 | keyboard will change keyboard will show keyboard did change keyboard did show |
12 | keyboard will change keyboard will hide keyboard did change keyboard did hide |
show和
hide的对应事件之前。而在大小不同的键盘之间切换时,除了发送
change事件外,还会发送
show事件(不发送
hide事件)。
另外还有两点需要注意的是:
如果是在两个大小相同的键盘之间切换,则不会发送任何消息
如果是普通键盘中类似于中英文键盘的切换,只要大小改变了,都会发送一组或多组与上面2)相同流程的消息
了解了事件的调用顺序,我们就可以根据自己的需要来决定在哪个消息处理方法中来执行操作。为此,我们需要获取一些有用的信息。这些信息是封装在通知的
userInfo中,通过上面常量
key来获取相关的值。通常我们关心的是
UIKeyboardFrameEndUserInfoKey,来获取动画完成后,键盘的
frame,以此来计算我们的
scroll view的高度。另外,我们可能希望
scroll view高度的变化也是通过动画来过渡的,此时
UIKeyboardAnimationCurveUserInfoKey和
UIKeyboardAnimationDurationUserInfoKey就有用了。
我们可以通过以下方式来获取这些值:
12 | if let dict = notification.userInfo { var animationDuration: NSTimeInterval = 0 var animationCurve: UIViewAnimationCurve = .EaseInOut var keyboardEndFrame: CGRect = CGRectZero dict[UIKeyboardAnimationCurveUserInfoKey]?.getValue(&animationCurve) dict[UIKeyboardAnimationDurationUserInfoKey]?.getValue(&animationDuration) dict[UIKeyboardFrameEndUserInfoKey]?.getValue(&keyboardEndFrame) ...... } |
userInfo中还有另外三个值,只不过这几个值从
iOS 3.2开始就已经废弃不用了。所以我们不用太关注。
最后说下表单。一个表单界面看着比较简单,但交互和UI总是能想出各种方法来让它变得复杂,而且其实里面设计到的细节还是很多的。像我们金融类的
App,通常都会涉及到大量的表单输入,所以如何做好,还是需要花一番心思的。空闲时,打算总结一下,写一篇文章。
参考
UIWindow Class Reference
零碎
自定义UIPickerView的行
UIPickerView的主要内容实际上并不多,主要是一个
UIPickerView类和对应的
UIPickerViewDelegate,
UIPickerViewDataSource协议,分别表示代理和数据源。在此不细说这些,只是解答我们遇到的一个小需求。
通常,
UIPickerView是可以定义多列内容的,比如年、月、日三列,这些列之间相互不干扰,可以自已滚自己的,不碍别人的事。不过,我们有这么一个需求,也是有三列,但这三列需要一起滚。嗯,这个就需要另行处理了。
在
UIPickerViewDelegate中,声明了下面这样一个代理方法:
12 | - (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view |
12 | - (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view { PickerViewCell *pickerCell = (PickerViewCell *)view; if (!pickerCell) { NSInteger column = 3; pickerCell = [[PickerViewCell alloc] initWithFrame:(CGRect){CGPointZero, [UIScreen mainScreen].bounds.size.width, 45.0f} column:column]; } [pickerCell setLabelTexts:@[...]]; return pickerCell; } |
PickerViewCell视图,里面根据我们的传入的
column参数来等分放置
column个
UILabel,并通过
setLabelTexts来设置每个
UILabel的文本。当然,我们也可以在
PickerViewCell去定义
UILabel的外观显示。就是这么简单。
不过,还有个需要注意的就是,虽然看上去是显示了
3列,但实际上是按
1列来处理的,所以下面的实现应该是返回
1:
12 | - (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView { return 1; } |
参考
UIPickerViewDelegate Protocol Reference
Swift中”[AnyObject]? does not have a member named generator” 问题的处理
有个小需求,需要遍历当前导航控制器栈的所有ViewController。
UINavigationController类自身的
viewControllers属性返回的是一个
[AnyObject]!数组,不过由于我的导航控制器本身有可能是
nil,所以我获取到的
ViewController数组如下:
1 | var myViewControllers: [AnyObject]? = navigationController?.viewControllers |
myViewControllers是一个
[AnyObject]?可选类型,这时如果我直接去遍历
myViewControllers,如下代码所示
12 | for controller in myViewControllers { ... } |
1 | [AnyObject]? does not have a member named "Generator" |
[AnyObject]?还是其它的诸如
[String]?类型,都会报这个错。其原因是可选类型只是个容器,它与其所包装的值是不同的类型,也就是说
[AnyObject]是一个数组类型,但
[AnyObject]?并不是数组类型。我们可以迭代一个数组,但不是迭代一个非集合类型。
在stackoverflow上有这样一个有趣的比方,我犯懒就直接贴出来了:
To understand the difference, let me make a real life example: you buy a new TV on ebay, the package is shipped to you, the first thing you do is to check if the package (the optional) is empty (nil). Once you verify that the TV is inside, you have to unwrap
it, and put the box aside. You cannot use the TV while it’s in the package. Similarly, an optional is a container: it is not the value it contains, and it doesn’t have the same type. It can be empty, or it can contain a valid value.
所以,这里的处理应该是:
12 | if let controllers = myViewControllers { for controller in controllers { ...... } } |
相关文章推荐
- iOS知识小集 第二期
- iOS 获取设备或系统信息
- iOS中设置导航栏标题的字体颜色和大小
- iOS知识小集 第一期
- iOS中定时任务的三种方法
- iOS 开发学习资料整理
- NSInvocation
- [IOS 开发] block运行结束,得到一个通知.
- 来源于 一般提问 分类 iOS/mac开发的一些知名个人博客
- iOS 数据缓存
- iOS webservice整理
- iOS开发系列--IOS程序开发概览
- 邓白氏编码(duns number)申请入口的路径-苹果开发者申请必
- 今天开始IOS开发, 三天热度
- iOS--viewController
- iOS推送处理
- iOS--(转)集成银联3.3.0
- iOS--(转)集成银联3.3.0
- IOS-TextField控件详解
- iOS image和string的相互转换