1- Introduction
作为iOS开发者,必须掌握:
- swift 语言
- 重要概念 - 代理,归档等
- 框架
MVC pattern
Interface Builder
- document outline
- canvas
Xcode utility area
scheme
Auto Layout
- 技巧:多选添加多个约束
Application Icons
- size
Launch Screen
2 - The Swift Language
Types
- structures
- classes
- enumerations
- 字典的key必须是
hashable
,保证key唯一,Int
,Float
,Character
,String
都是hashable
。 - 集合的元素唯一并且是
hashable
。 - literal values 是 instance。
- 初始化器:
- 空值
let emptyString = String() // ""
let emptyArrayOfInts = [Int]() // 0 elements
let emptySetOfFloats = Set<Float>() // 0 elements”
- 默认值
let defaultNumber = Int() // 0
let defaultBool = Bool() // false
let defaultFloat = Float() // 0.0
- 文档资料
- The Swift Programming Language
- The Swift Standard Library Reference
3 - Views and the View Hierarchy
- View Hierarchy
- 一旦层级关系确定,就开始绘制到屏幕上:
- 绘制自己到layer
- 所以的layer在屏幕上组合起来
一个 framework 是相关的类和资源的集合。
一个视图的frame是相对其父视图的。
Baseline :大部分是和bottom相同的,但是UITextField和UILabel是在其内容的底部。
Frame 和 Alignment rectangle
- Alignment rectangle 大部分和 Frame相同,但它是通过约束计算出来的。
4 - Text Input and Delegation
- 使用NSNumberFormatter 输出格式化数字的字符串
参考 iOS中数字的格式化 NSNumberFormatter
let numberFormatter: NSNumberFormatter = {
let nf = NSNumberFormatter()
nf.numberStyle = .DecimalStyle
nf.minimumFractionDigits = 0
nf.maximumFractionDigits = 1
return nf
}()
celsiusLabel.text = numberFormatter.stringFromNumber(value)
代理:处理多个callback。
代理协议的命名:类名+Delegate
避免输入两个
.
号
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let existingTextHasDecimalSeparator = textField.text?.rangeOfString(".")
let replacementTextHasDecimalSeparator = string.rangeOfString(".")
if existingTextHasDecimalSeparator != nil && replacementTextHasDecimalSeparator != nil {
return false
}
else {
return true
}
}
- 限制只能输入特定的字符 ???
5 - View Controllers
当一个View controller 被设置为window的 rootViewController ,它的view 被添加到window的视图层级中。
A UITabBarController’s view is a UIView with two subviews: the tab bar and the view of the selected view controller
生命周期
- init(coder:) :用 storyboard 创建 或 init(nibName:bundle:):用代码创建
- loadView()
- viewDidLoad()
- viewWillAppear(_: )
- viewDidAppear(_:)
- viewWillDisappear(_:)
- viewDidDisappear(_:)
6 - Programmatic Views
- Constraints Common ancestor
- 约束需要加到最近的共同祖先上。
- active属性的作用:先找共同祖先,然后调用addConstraint(:) 或 removeConstraint(:)
- layoutGuide
- 使用topLayoutGuide:让内容不遮挡status bar 或 navigation bar
- 使用bottomLayoutGuide: 让内容不遮挡 tab bar
- 可以使用的约束:topAnchor, bottomAnchor, heightAnchor
- layoutMarginsGuide
- 每个视图都有这个属性
7 - Localization
国际化
- NSNumberFormatter
- 有一个local属性,被设置为设备的当前local,使用NSNumberFormatter时,它会首先检查它的local属性,然后格式化设置内容。
- NSLocal 知道不同地区使用的symbols, dates, decimals and whether use metric system。
@IBAction func fahrenheitFieldEditingChanges(textField: UITextField) {
if let text = textField.text, let number = numberFormatter.numberFromString(text) {
fahrenheitValue = number.doubleValue
}
else {
fahrenheitValue = nil
}
}
- NSLocal.currentLocal() 代表用户设置的地区。
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let currentLocale = NSLocale.currentLocale()
let decimalSeparator =
currentLocale.objectForKey(NSLocaleDecimalSeparator) as! String
let existingTextHasDecimalSeparator = textField.text?.rangeOfString(decimalSeparator)
let replacementTextHasDecimalSeparator = string.rangeOfString(decimalSeparator)
if existingTextHasDecimalSeparator != nil && replacementTextHasDecimalSeparator != nil {
return false
}
else {
return true
}
}
本地化
NSLocal 没有的,只有本地化。
- 调试技巧:
- Edit Scheme... -> Run -> Options -> Application Region
- Show Assistant Editor -> Preview -> 右下角可以选择语言
- 添加本地化后 -> 删除应用 -> 重启xcode -> Clean -> Run
- 本地化的原理:main bundle 里放了所有的资源文件,本地化资源文件就是复制一份放在 bundle 的具体语言的目录下(目录名为语言和地区的简写,后缀为iproj)。当需要资源的时候,先查找bundle的最顶层,如果找到,返回文件的URL,如果没有找到,则根据地区和语言设置找到相应的iproj目录,查找需要的资源,如果找到,返回URL,如果没有找到,则查找Base.iproj目录,如果没有找到,则返回nil。(为什么使用调试技巧3)
- storyboard 怎么办?Xcode 创建了 Base.iproj 目录将storyboard 放在里面,如需本地化只需创建 Localizable.strings 文件(只本地化字符串)。
- 界面可以通过Autolayout本地化。
storyboard 本地化步骤
-
选中右侧Localization -> 选中English
Project -> Info -> + -> 添加其他语言
翻译对应语言
使用调试技巧2,选择Application Language
代码中的字符串本地化步骤
在代码中使用NSLocalizedString函数
let str = NSLocalizedString("hello world", comment: "xxxxddd")
用终端进入代码文件目录
输入命令:genstrings ViewController.swift
将生成的文件Localizable.strings拖入项目
选中右侧Localization -> 点击Localize... -> Base
勾选其他语言
翻译对应语言
使用调试技巧2,选择Application Language
使用XLIFF文件
导出:Project -> Editor -> Export For Localization...
翻译
导入:Project -> Editor -> Import For Localization...
8 - Controlling Animations
-
约束动画:layoutIfNeeded -> 修改动画 -> layoutIfNeeded
技巧:使用swap(_:)函数。view.layoutIfNeeded() let screenWidth = view.frame.width self.nextQuestionLabelCenterXConstraint.constant = 0 self.currentQuestionLabelCenterXConstraint.constant += screenWidth UIView.animateWithDuration(0.5, delay: 0, options: [.CurveLinear], animations: { self.currentQuestionLabel.alpha = 0 self.nextQuestionLabel.alpha = 1 self.view.layoutIfNeeded() }, completion: { _ in swap(&self.currentQuestionLabel, &self.nextQuestionLabel) swap(&self.currentQuestionLabelCenterXConstraint, &self.nextQuestionLabelCenterXConstraint) self.updateOffScreenLabel() })
9 - TableViewControllers
- UITableViewController
- view controller
- data source
-
delegate
-
MVC
- 使用依赖注入:初始化传入(纯代码)或者通过属性传入(stroyboard)。
-
UITableViewCell 层级关系
重用机制
- 注册
- dequeue
首先根据identifier查看重用池(队列),如果有,则dequeue,如果没有,系统根据注册的类型创建。
-
Content Insets
// Get the height of the status bar
let statusBarHeight = UIApplication.sharedApplication().statusBarFrame.heightlet insets = UIEdgeInsets(top: statusBarHeight, left: 0, bottom: 0, right: 0) tableView.contentInset = insets tableView.scrollIndicatorInsets = insets
设计模式
- Delegation
- Data source
- Model - View - Controller
- Target - action pairs
- dynamic type
-
storyboard(静态)
- code (动态)
func updateLabels() {
let bodyFont = UIFont.preferredFontForTextStyle(UIFontTextStyleBody)
nameLabel.font = bodyFont
valueLabel.font = bodyFont
let caption1Font = UIFont.preferredFontForTextStyle(UIFontTextStyleCaption1)
serialNumberLabel.font = caption1Font
}
- stack view
技巧:nested stackView + intrinsic size + content hugging priority + content compression resistance priority + auto layout
10 - NavigationController
- 触发segue
- action item(button, tableViewCell, other UIControl) :storyboard 触发
- identifier : 代码触发,判断segue
- 传值: tableView.indexPathForSelectedRow
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// If the triggered segue is the "ShowItem" segue
if segue.identifier == "ShowItem" {
// Figure out which row was just tapped
if let row = tableView.indexPathForSelectedRow?.row {
// Get the item associated with this row and pass it along
let item = itemStore.allItems[row]
let detailViewController = segue.destinationViewController as! DetailViewController
detailViewController.item = item
}
}
}
- endEditing(_:):关闭键盘的便利方法。
- 不用关心哪一个 text field 是响应者。它会检查自己层级中是否有是第一响应者的 text field,如果有,则对其调用 resignFirstResponder()
- 重写becomeFirstResponder(),自定义 text field 的 borderStyle
- 每一个UIViewController 有一个 navigationItem 属性,它不是UIView的子类,而是为navigation bar 提供内容。navigation bar 根据 view controller 的navigationItem 配置自己。UIBarButtonItem 类似。
11 - Camera
- UIImagePickerController
设置 sourceType
- Camera:使用相机
- PhotoLibrary:使用相册
-
SavedPhotosAlbum:使用最近拍照
@IBAction func takePicture(sender: UIBarButtonItem) {
let imagePicker = UIImagePickerController()
// If the device has a camera, take a picture, otherwise,
// just pick from photo library
if UIImagePickerController.isSourceTypeAvailable(.Camera) {
imagePicker.sourceType = .Camera
}
else {
imagePicker.sourceType = .PhotoLibrary
}
imagePicker.delegate = self
// Place image picker on the screen
presentViewController(imagePicker, animated: true, completion: nil)
}
func imagePickerController(picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [String: AnyObject]) {
// Get picked image from info dictionary
let image = info[UIImagePickerControllerOriginalImage] as! UIImage
// Store the image in the ImageStore for the item's key
imageStore.setImage(image, forKey:item.itemKey)
// Put that image onto the screen in our image view
imageView.image = image
// Take image picker off the screen -
// you must call this dismiss method
dismissViewControllerAnimated(true, completion: nil)
}
- Cache
将image 存储在内存,如果收到low-memory 通知,则释放占有的资源。
12 - Archiving
界面文件的对象是arhieved。
数字使用:encodeInt(intv: Int32, forKey key: String)
字符串使用:encodeObject(objv: AnyObject?, forKey key: String)
原因:虽然String不是对象,但是它和NSString 桥接了,会自动转换。-
Encoding 是递归操作
所有对象都必须遵守NSCoding协议!
-
沙盒
Library/Preferences 是自动被NSUserDefaults管理。
使用DocumentDirectory和first的原因
iOS和OS X共用一套API。
let itemArchiveURL: NSURL = {
let documentsDirectories =
NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory,
inDomains: .UserDomainMask)
let documentDirectory = documentsDirectories.first!
return documentDirectory.URLByAppendingPathComponent("items.archive")
}()application state
image data
UIImageJPEGRepresentation(image, 0.5)
第二个参数为压缩质量。1.0 是最高质量,少压缩。
data.writeToURL(imageURL, atomically: true)
第二个参数如果为true,文件先写到文件的临时地方,一旦写操作完成,文件重命名为URL,替换以前存在的文件。bundle
bundle中的文件是只读的,不能修改,在运行时也不能动态添加文件。
13 - SizeClasses
- 改变sizeClasses可以改变:
- 视图的属性 (比如stackView)
- subview 是否 installed
- constraint 是否 installed
- constraint 的 constant
- subview 的字体
14 - TouchEvents
1.默认状态,view一次只能接受一个touch。一旦开始,其他touch的touch生命周期方法ignored。
- UIMenuController
- only one UIMenuController per application
- has list of UIMenuItem
- item has a title and action
- action message sends the first responder of the window
func tap(gestureRecognizer: UIGestureRecognizer) {
print("Recognized a tap")
let point = gestureRecognizer.locationInView(self)
selectedLineIndex = indexOfLineAtPoint(point)
// Grab the menu controller
let menu = UIMenuController.sharedMenuController()
if selectedLineIndex != nil {
// Make ourselves the target of menu item action messages
becomeFirstResponder()
// Create a new "Delete" UIMenuItem
let deleteItem = UIMenuItem(title: "Delete", action: "deleteLine:")
menu.menuItems = [deleteItem]
// Tell the menu where it should come from and show it
menu.setTargetRect(CGRect(x: point.x, y: point.y, width: 2, height: 2),
inView: self)
menu.setMenuVisible(true, animated: true)
}
else {
// Hide the menu if no line is selected
menu.setMenuVisible(false, animated: true)
}
setNeedsDisplay()
}
func deleteLine(sender: AnyObject) {
// Remove the selected line from the list of finishedLines
if let index = selectedLineIndex {
finishedLines.removeAtIndex(index)
selectedLineIndex = nil
// Redraw everything
setNeedsDisplay()
}
}
override func canBecomeFirstResponder() -> Bool {
return true
}
14 - CollectionView
- the collection view will be pinned to the top of the entire view instead of to the top layout guide. This is useful for scroll views (and their subclasses, like UITableView and UICollectionView) so that the content will scroll underneath the navigation bar. The scroll view will automatically update its insets to make the content visible.