第1章 Interface Bundle 概要
Bundle
一種標(biāo)準(zhǔn)化的層次結(jié)構(gòu),保存了可執(zhí)行代碼及代碼所需要的資源。
nib
Next Interface Builder
Interface Builder 的優(yōu)點(diǎn)
- 開發(fā)和維護(hù)效率高
- 減少大量的 UI 代碼和“膠水代碼”
- 適配變得十分簡(jiǎn)單
- IB 也可以做一些非 UI 的事情
- 利用 IB 學(xué)習(xí)控件可以達(dá)到事半功倍的效果
Interface Builder 的缺點(diǎn)
- IB 的執(zhí)行效率沒有純代碼高
- 使用 IB 開發(fā)的過(guò)程中容易出現(xiàn)一些小問(wèn)題
- 有一定的學(xué)習(xí)成本
- 文件易沖突
- 沒有代碼表達(dá)清晰
- 不利于代碼的封閉和工程架構(gòu)的組織
Interface Builder 學(xué)習(xí)的特點(diǎn)
- 簡(jiǎn)單,容易入門
- 容易犯錯(cuò)誤
- 有很多的“坑”,需要積累屬于自己的經(jīng)驗(yàn)
Interface Builder 的發(fā)展
xib -> sb -> AutoLayout -> LaunchScreen.storyboard
蘋果越來(lái)越重視 IB。
CocoaPods
# 1 最新版本
pod 'AFNetworking'
# 2 最新的2.x版本
pod 'AFNetworking', '~>2.5.3'
# 3 指定版本
pod 'AFNetworking', '2.5.3'
試用
$ pod try AFNetworking
Podfile文件與CocoaPods的三種依賴方式
- 遠(yuǎn)程依賴
# master
pod 'Alamofile', :git => 'https://github.com/Alamofile/Alamofile.git'
# branch
pod 'Alamofile', :git => 'https://github.com/Alamofile/Alamofile.git', :branch -> 'dev'
# commit
pod 'Alamofile', :git => 'https://github.com/Alamofile/Alamofile.git', :commit => '0f506b1c45'
- 本地依賴
pod 'AFNetworking', :path => '../externals/libs/AFNetworking'
- 私有依賴
pod 'AFNetworking', :podspec => '../externals/libs/AFNetworking.podspec'
“:podspec=>”用于指定本地的一個(gè)xxx.podspec文件。
podspec文件
$ pod init
$ cat Podfile
第2章 使用 Interface Builder
解決沖突
- 解決普通IB文件沖突
Open As -> Source Code
<<<<<<<
=======
>>>>>>>
編輯好,再刪除這三行就可以了。
- 解決 Xcode 8 引起的 IB 文件沖突
如果用 Source Code 不能打開,就用文件編輯器(vim, etc)打開,把systemVersion等沖突解決。
關(guān)聯(lián) xib 文件與源文件
- 關(guān)聯(lián) xib 文件與 UIView 子類的源文件
- 新建一個(gè)空的xib文件,拖一個(gè)UIView上去
- 新建一個(gè)繼承自UIView的源文件
- 選中xib文件里的View,把class改為上面
@IBOutlet 與 @IBAction
連線
設(shè)計(jì)模式之MVC

理解 File's Owner
關(guān)聯(lián) xib 文件與 UIViewController 子類的源文件
- 自定義一個(gè) VC 的 View 的兩種方法
- 在 IB 文件中選中 VC 所在的 View,在 Show the Identity inspector 中設(shè)置 Class 標(biāo)簽的值為自定義的 View 的類名。
- 在源文件的 loadView() 方法里設(shè)置該 VC 的 View 屬性為這個(gè)自定義的 View。
override func loadView() {
self.view = CustomView.init(frame: UIScreen.main.bounds)
}
File's Owner 指向 VC
VC 的 View 連線
xib 既可以與 UIView 關(guān)聯(lián),也可以與 UIViewController 關(guān)聯(lián),也可以同時(shí)關(guān)聯(lián) UIView 與 UIViewController
使用 xib
- 通過(guò) Bundle 方式加載
- 通過(guò) UINib 方式加載
使用與UIView子類源文件關(guān)聯(lián)的xib
Bundle
- (NSArray *)loadNibNamed:(NSString *)name
owner:(id)owner
options:(NSDictionary *)options;
func loadNibNamed(_ name: String,
owner: Any?,
options: [AnyHashable : Any]? = nil) -> [Any]?
Parameters 參數(shù)
- name
The name of the nib file, which need not include the .nib extension.
nib名稱 - owner
The object to assign as the nib’s File's Owner object.
如果xib文件有File's Owner,一定傳其實(shí)例對(duì)象,否則傳nil - options
A dictionary containing the options to use when opening the nib file. For a list of available keys for this dictionary, see UIKit Nib Loading Options.
Bundle Demo
let testView = Bundle.main.loadNibNamed("TestView", owner: nil, options: nil)?[0] as! UIView
view.addSubview(testView)
UINib
NS_CLASS_AVAILABLE_IOS(4_0) @interface UINib : NSObject
// If the bundle parameter is nil, the main bundle is used.
// Releases resources in response to memory pressure (e.g. memory warning), reloading from the bundle when necessary.
+ (UINib *)nibWithNibName:(NSString *)name bundle:(nullable NSBundle *)bundleOrNil;
// If the bundle parameter is nil, the main bundle is used.
+ (UINib *)nibWithData:(NSData *)data bundle:(nullable NSBundle *)bundleOrNil;
// Returns an array containing the top-level objects from the NIB.
// The owner and options parameters may both be nil.
// If the owner parameter is nil, connections to File's Owner are not permitted.
// Options are identical to the options specified with -[NSBundle loadNibNamed:owner:options:]
- (NSArray *)instantiateWithOwner:(nullable id)ownerOrNil options:(nullable NSDictionary *)optionsOrNil;
@end
@available(iOS 4.0, *)
open class UINib : NSObject {
// If the bundle parameter is nil, the main bundle is used.
// Releases resources in response to memory pressure (e.g. memory warning), reloading from the bundle when necessary.
public /*not inherited*/ init(nibName name: String, bundle bundleOrNil: Bundle?)
// If the bundle parameter is nil, the main bundle is used.
public /*not inherited*/ init(data: Data, bundle bundleOrNil: Bundle?)
// Returns an array containing the top-level objects from the NIB.
// The owner and options parameters may both be nil.
// If the owner parameter is nil, connections to File's Owner are not permitted.
// Options are identical to the options specified with -[NSBundle loadNibNamed:owner:options:]
open func instantiate(withOwner ownerOrNil: Any?, options optionsOrNil: [AnyHashable : Any]? = nil) -> [Any]
}
UINib Demo
let testViewNib = UINib.init(nibName: "TestView", bundle: Bundle.main)
func loadTestView() {
let testView = UINib.instantiate(withOwner: nil, options: nil)[0] as! UIView
view.addSubview(testView)
}
使用與 UIViewController 子類源文件關(guān)聯(lián)的 xib
Demo HomeViewController.swift <==> HomeController.xib
let homeVC = HomeViewController()
class HomeViewController {
var aName: String?
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: "HomeController", bundle: nil)
}
init(aName: String) {
name = aName
super.init(nibName: "HomeController", bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
嵌套 xib
https://github.com/iOSDevLog/iOSDevLog/tree/master/228.%20NestedXib
圖片上傳失敗請(qǐng)。參考 http://iosdevlog.com/ios/2017/12/19/ios-ui-interface-builder.html
使用 storyboard
@available(iOS 5.0, *)
open class UIStoryboard : NSObject {
public /*not inherited*/ init(name: String, bundle storyboardBundleOrNil: Bundle?)
open func instantiateInitialViewController() -> UIViewController?
open func instantiateViewController(withIdentifier identifier: String) -> UIViewController
}
storyboard Demo
extension UIViewController {
class func storyboardID() -> String {
return String(describing: self)
}
}
let userStoryBoard = UIStoryboard(name: "User", bundle: nil)
let profileVC = userStoryBoard.instantiateInitialViewController(withIdentifier: ProfileController.storyboardID())
第3章 全面學(xué)習(xí) xib
Autoresizing

默認(rèn)選中左,上。
對(duì)應(yīng)代碼為:
testView.autoresizingMask = [.flexibleRightMargin, .flexibleBottomMargin]
外框的 上、下、左、右如果選中,則UIView的邊框與父View邊框距離保持不變。
中間帶箭頭的選中表示UIView邊框是隨屏幕尺寸變化的。
否則UIView大小保持不變。
也可以看右側(cè)的動(dòng)畫查看顯示效果。
第4章 在 Interface Builder 中使用 Auto Layout
在 IB 中使用 Auto Layout 的優(yōu)缺點(diǎn)
設(shè)置約束十分簡(jiǎn)單
如果約束不恰當(dāng),IB 提供很好的實(shí)時(shí)反饋
如果約束不恰當(dāng),IB 可以幫忙改正。改正操作十分簡(jiǎn)單、方便、快捷。
難以理解
約束
Auto Layout 的數(shù)學(xué)公式
item1.attribute1 = multiplier * item2.attribute2 + constant
約束屬性
public enum NSLayoutAttribute : Int {
case left
case right
case top
case bottom
case leading
case trailing
case width
case height
case centerX
case centerY
case lastBaseline
@available(iOS 8.0, *)
case firstBaseline
@available(iOS 8.0, *)
case leftMargin
@available(iOS 8.0, *)
case rightMargin
@available(iOS 8.0, *)
case topMargin
@available(iOS 8.0, *)
case bottomMargin
@available(iOS 8.0, *)
case leadingMargin
@available(iOS 8.0, *)
case trailingMargin
@available(iOS 8.0, *)
case centerXWithinMargins
@available(iOS 8.0, *)
case centerYWithinMargins
case notAnAttribute
}
約束關(guān)系
public enum NSLayoutRelation : Int {
case lessThanOrEqual
case equal
case greaterThanOrEqual
}
約束的優(yōu)先級(jí)
public struct UILayoutPriority : RawRepresentable, Equatable, Hashable {
public init(_ rawValue: Float)
public init(rawValue: Float)
}
extension UILayoutPriority {
@available(iOS 6.0, *)
public static let required: UILayoutPriority
@available(iOS 6.0, *)
public static let defaultHigh: UILayoutPriority // This is the priority level with which a button resists compressing its content.
@available(iOS 6.0, *)
public static let defaultLow: UILayoutPriority // This is the priority level at which a button hugs its contents horizontally.
@available(iOS 6.0, *)
public static let fittingSizeLevel: UILayoutPriority // When you send -[UIView systemLayoutSizeFittingSize:], the size fitting most closely to the target size (the argument) is computed. UILayoutPriorityFittingSizeLevel is the priority level with which the view wants to conform to the target size in that computation. It's quite low. It is generally not appropriate to make a constraint at exactly this priority. You want to be higher or
lower.
}
Instrinsic Size 固有尺寸
默認(rèn)設(shè)置了 Width 和 Height
Content Compression Resistance 壓縮阻力
Content Hugging 內(nèi)容吸附
NSLayoutConstraint 與 @IBOutlet 連線
設(shè)置約束的方法
- 在 IB 中設(shè)置 - 推薦
- 蘋果原生 API - 最復(fù)雜,強(qiáng)烈不推薦
- 用 VFL (Visual Format Language) 設(shè)置約束 - 不推薦
- 第三方庫(kù)(Masonry等)設(shè)置約束 - 代碼設(shè)置最簡(jiǎn)單、最常用 推薦(如果不熟悉 Auto Layout)
UIStackView
Axis
- Vertical: 豎直布局
- Horizontal: 水平布局
Alignment
- Fill
- Top
- Leading
- Center
- Bottom
- Trailing
- First Baseline
- Last Base Line
Distribution
- Fill
- Fill Equal
- Fill Proportionally
- Equal Spacing
- Equal Centering
Space
Baseline Relative
FDStackView
NSUUID https://gist.github.com/OliverLetterer/4643294
Auto Layout 的異類 - UIScrollView
UIScrollView 的子 View 需要設(shè)置 6 個(gè)約束
scrollView.contentSize.width = subView.leading + subView.width + subView.trailing;
scrollView.contentSize.height = subView.top + subView.height+ subView.bottom;
設(shè)置 ScrollView 的子 View 約束時(shí)一定要讓系統(tǒng)確定 ScrollView 的 contentSize。
第5章 storyboard 全面學(xué)習(xí)
Extra View
segue
Embed Segue
Unwind Segue
Launch Screen
https://github.com/iOSDevLog/iOSDevLog/tree/master/229.%20ScreenLunch
@interface AppDelegate ()
@property (nonatomic, strong) UIWindow* launchWindow;
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.launchWindow = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
self.launchWindow.hidden = NO;
self.launchWindow.windowLevel = UIWindowLevelNormal + 1;
self.launchWindow.rootViewController = [UIStoryboard storyboardWithName:@"Launch Screen" bundle:nil].instantiateInitialViewController;
[UIView animateWithDuration:3 delay:0.5 options: UIViewAnimationOptionCurveEaseInOut animations:^{
self.launchWindow.alpha = 0;
} completion:^(BOOL finished) {
self.launchWindow.hidden = YES;
self.launchWindow.windowLevel = UIWindowLevelNormal - 1;
self.launchWindow.alpha = 1;
}];
return YES;
}
@end
var launchWindow: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
launchWindow = UIWindow(frame: UIScreen.main.bounds)
launchWindow?.windowLevel = UIWindowLevelNormal + 1
launchWindow?.isHidden = false
launchWindow?.rootViewController = UIStoryboard(name: "LaunchScreen", bundle: nil).instantiateInitialViewController()
UIView.animate(withDuration: 0.5, delay: 3, options: .curveEaseInOut, animations: {
self.launchWindow?.alpha = 0
}) { (finish) in
if finish {
self.launchWindow?.isHidden = true
self.launchWindow?.windowLevel = UIWindowLevelNormal - 1
self.launchWindow?.alpha = 1
}
}
return true
}
第6章 Interface Builder 進(jìn)階
Use Trait Variations
| 設(shè)備 | 豎屏 | 橫屏 |
|---|---|---|
| 3.5 iPhone | wC hR | wC hC |
| 4.0 iPhone | wC hR | wC hC |
| 4.7 iPhone | wC hR | wC hC |
| 5.5 iPhone | wC hR | wR hC |
| iPad | wR hR | wR hR |
User Define Runtime Attribute
| IB 中的類型 | Swift | Objective-C |
|---|---|---|
| Boolean | Bool | BOOL |
| Number | + | + |
| String | String | NSString |
| Localized String | String | NSString |
| Point | CGPoint | CGPoint |
| Size | CGSize | CGSize |
| Rect | CGRect | CGRect |
| Range | Range | NSRange |
| Color | UIColor | UIColor |
| Image | UIImage | UIImage |
| Nil | Nil | Nil |
Number 在 Swift 里面可以對(duì)應(yīng) Int、Double、Float。
在 Objective-C 里面可以對(duì)應(yīng) NSInteger、NSNumber 等。
extension UIView {
var borderColor: UIColor {
set {
self.layer.borderColor = newValue.cgColor
}
get {
return UIColor.init(cgColor: self.layer.borderColor!)
}
}
}
IB 文件的加載過(guò)程
Bundle 和 UINib
- 將 nib 加載到內(nèi)存
- 解固化并實(shí)例化 nib 文件里對(duì)應(yīng)的對(duì)象
- 建立 connections (outlet、action)
- 調(diào)用 awakeFromNib() 方法
- 將 nib 中可見的控件顯示出來(lái)
本地化
兩種策略
- App 本地化跟隨系統(tǒng)語(yǔ)言
- App 內(nèi)部有一個(gè)可以設(shè)置語(yǔ)言的選項(xiàng)
本地化介紹
Base
文本的本地化
- 利用 NSLocalizedString。 新建 Localizable.strings 文件
Localizable.strings(English)
"test" = "hello world";
Localizable.strings(Chinese(Simplified))
"test" = "你好,世界";
override func viewDidLoad() {
super.viewDidLoad()
// Localizable.string
testLabel.text = NSLocalizedString("test", comment: "")
// Home.strings
// testLabel.text = NSLocalizedString("test", tableName: "Home", comment: "")
}
Info.plist 的本地化
新建 InfoPlist.strings,在 Show the File inspector 點(diǎn)擊 Localize...
InfoPlist.strings(English)
CFBundleName = "hello world";
CFBundleDisplayName = "hello world";
InfoPlist.strings(Chinese(Simplified))
CFBundleName = "你好,世界";
CFBundleDisplayName = "你好,世界";
圖片資源的本地化
- 方法1
Localizable.strings(English)
"testImageName" = "1";
Localizable.strings(Chinese(Simplified))
"testImageName" = "2";
testImageView.image = UIImage.init(named: NSLocalizedString("testImageName", comment: "")
- 方法2
選中圖片,Show the File inspector 點(diǎn)擊 localize...,替換 zh-Hans.lproj 中的資源文件。
App 內(nèi)設(shè)置語(yǔ)言的本地化
https://github.com/iOSDevLog/iOSDevLog/tree/master/230.%20Localizations
extension Bundle {
class func loadLocalizableString(languageBundleName: String, key: String) -> String? {
let languageBundlePath = Bundle.main.path(forResource: languageBundleName, ofType: "lproj")
guard languageBundlePath != nil else {
return nil
}
let languageBundle = Bundle.init(path: languageBundlePath!)
guard languageBundle != nil else {
return nil
}
let value = languageBundle?.localizedString(forKey: key, value: "", table: "")
return value
}
}
func changeLanguage() {
let kTestKey = "testKey"
switch selectIndex {
case 0:
testLabel.text = Bundle.loadLocalizableString(languageBundleName: Language.simplifiedChinese.rawValue, key: kTestKey)
break
case 1:
testLabel.text = Bundle.loadLocalizableString(languageBundleName: Language.english.rawValue, key: kTestKey)
break
case 2:
testLabel.text = Bundle.loadLocalizableString(languageBundleName: Language.japanese.rawValue, key: kTestKey)
break
case 3:
testLabel.text = Bundle.loadLocalizableString(languageBundleName: Language.korea.rawValue, key: kTestKey)
break
default:
break
}
}
Storyboard Reference 的使用
使用 RBStoryboardLink https://github.com/rob-brown/RBStoryboardLink
用 Object 重構(gòu) "神VC"
代碼量龐大、結(jié)構(gòu)臃腫、可維護(hù)性差的 VC。
使用 Object
- 通常 VC 會(huì)成為很多對(duì)象的 delegate,需要處理很多回調(diào)。用 Object 替 VC 實(shí)現(xiàn) delegate。
- 將一些能用需求或交互模塊化在對(duì)應(yīng)的 Object 里。將需求或交互與 VC 解耦。
用 External Object 重構(gòu) VC
只能在于 xib
IB 中的關(guān)鍵字總結(jié)
Swift
- @IBAction
- @IBOutlet
- @IBDesignable
- @IBInspectable
Objective-C
- IBAction
- IBOutlet
- IB_DESIGNABLE
- IBInspectable
- IBOutletCollection(ClassName)
@IBDesignable
可以不運(yùn)行程序的情況下把源文件中的一些代碼實(shí)時(shí)渲染到 IB 中,但是源文件必須是 UIView 或者 NSView 的子類。
prepareForInterfaceBuild()
只需要將實(shí)時(shí)渲染的代碼放到 prepareForInterfaceBuild() 方法中就可以了,該方法并不會(huì)在程序運(yùn)行時(shí)調(diào)用。
@IBInspectable
用 @IBInspectable 修飾的屬性會(huì)顯示在 IB 的 Show the Attributes inspector。
extension UIView {
private struct AssociatedKeys {
static var name: String?
}
// 存儲(chǔ)屬性
@IBInspectable var name: String {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.name) as! String
}
set {
objc_setAssociatedObject(self, &AssociatedKeys.name, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
// 計(jì)算屬性
@IBInspectable var borderColor: UIColor {
set {
self.layer.borderColor = newValue.cgColor
}
get {
return UIColor.init(cgColor: self.layer.borderColor!)
}
}
}
第7章 在 Interface Builder 開發(fā)中的技巧和 Bug
調(diào)整 View 的尺寸,使它與顯示內(nèi)容的尺寸相適應(yīng)
comment + =
查看各個(gè) View 之間的距離
選中 View, 按住 option,懸停在其它 View 上。
在 IB 中添加參考線
Editor -> Guides -> Add Horizontal Line
command + -
Editor -> Guides -> Add Vertical Line
command + ctrl + |
快速調(diào)整底層被擋住的 View 的位置
快速查看 View 的 UI 層次關(guān)系
command + shift + right click
連線小技巧
兩個(gè)窗口
使用吸管快速設(shè)置顏色
IB 中的復(fù)制與粘貼
command + c
command + v
利用 Media Library 快速設(shè)置圖片
IB 開發(fā)中遇到的一些小 bug
最好的做法就是重啟 Xcode。
- 無(wú)法連線
IB 文件是否與源文件關(guān)聯(lián)
- @IBAction 紅色提示
先在源文件中定義好方法,再?gòu)脑次募?拖 到 IB 文件進(jìn)行 連線