第1章 Interface Bundle 概要
Bundle
一種標準化的層次結(jié)構(gòu)催式,保存了可執(zhí)行代碼及代碼所需要的資源悬而。
nib
Next Interface Builder
Interface Builder 的優(yōu)點
- 開發(fā)和維護效率高
- 減少大量的 UI 代碼和“膠水代碼”
- 適配變得十分簡單
- IB 也可以做一些非 UI 的事情
- 利用 IB 學(xué)習控件可以達到事半功倍的效果
Interface Builder 的缺點
- IB 的執(zhí)行效率沒有純代碼高
- 使用 IB 開發(fā)的過程中容易出現(xiàn)一些小問題
- 有一定的學(xué)習成本
- 文件易沖突
- 沒有代碼表達清晰
- 不利于代碼的封閉和工程架構(gòu)的組織
Interface Builder 學(xué)習的特點
- 簡單呜舒,容易入門
- 容易犯錯誤
- 有很多的“坑”锭汛,需要積累屬于自己的經(jīng)驗
Interface Builder 的發(fā)展
xib -> sb -> AutoLayout -> LaunchScreen.storyboard
蘋果越來越重視 IB笨奠。
CocoaPods
# 1 最新版本
pod 'AFNetworking'
# 2 最新的2.x版本
pod 'AFNetworking', '~>2.5.3'
# 3 指定版本
pod 'AFNetworking', '2.5.3'
試用
$ pod try AFNetworking
Podfile文件與CocoaPods的三種依賴方式
- 遠程依賴
# 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=>”用于指定本地的一個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 子類的源文件
- 新建一個空的xib文件,拖一個UIView上去
- 新建一個繼承自UIView的源文件
- 選中xib文件里的View,把class改為上面
@IBOutlet 與 @IBAction
連線
設(shè)計模式之MVC
理解 File's Owner
關(guān)聯(lián) xib 文件與 UIViewController 子類的源文件
- 自定義一個 VC 的 View 的兩種方法
- 在 IB 文件中選中 VC 所在的 View配名,在 Show the Identity inspector 中設(shè)置 Class 標簽的值為自定義的 View 的類名啤咽。
- 在源文件的 loadView() 方法里設(shè)置該 VC 的 View 屬性為這個自定義的 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)宇整,也可以同時關(guān)聯(lián) UIView 與 UIViewController
使用 xib
- 通過 Bundle 方式加載
- 通過 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,一定傳其實例對象芋膘,否則傳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
圖片上傳失敗請鳞青。參考 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é)習 xib
Autoresizing
默認選中左,上为朋。
對應(yīng)代碼為:
testView.autoresizingMask = [.flexibleRightMargin, .flexibleBottomMargin]
外框的 上臂拓、下、左习寸、右如果選中胶惰,則UIView的邊框與父View邊框距離保持不變。
中間帶箭頭的選中表示UIView邊框是隨屏幕尺寸變化的霞溪。
否則UIView大小保持不變孵滞。
也可以看右側(cè)的動畫查看顯示效果精钮。
第4章 在 Interface Builder 中使用 Auto Layout
在 IB 中使用 Auto Layout 的優(yōu)缺點
設(shè)置約束十分簡單
如果約束不恰當,IB 提供很好的實時反饋
如果約束不恰當剃斧,IB 可以幫忙改正轨香。改正操作十分簡單、方便幼东、快捷臂容。
難以理解
約束
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)先級
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 固有尺寸
默認設(shè)置了 Width 和 Height
Content Compression Resistance 壓縮阻力
Content Hugging 內(nèi)容吸附
NSLayoutConstraint 與 @IBOutlet 連線
設(shè)置約束的方法
- 在 IB 中設(shè)置 - 推薦
- 蘋果原生 API - 最復(fù)雜,強烈不推薦
- 用 VFL (Visual Format Language) 設(shè)置約束 - 不推薦
- 第三方庫(Masonry等)設(shè)置約束 - 代碼設(shè)置最簡單根蟹、最常用 推薦(如果不熟悉 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 個約束
scrollView.contentSize.width = subView.leading + subView.width + subView.trailing;
scrollView.contentSize.height = subView.top + subView.height+ subView.bottom;
設(shè)置 ScrollView 的子 View 約束時一定要讓系統(tǒng)確定 ScrollView 的 contentSize脓杉。
第5章 storyboard 全面學(xué)習
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 進階
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 里面可以對應(yīng) Int、Double简逮、Float球散。
在 Objective-C 里面可以對應(yīng) NSInteger、NSNumber 等散庶。
extension UIView {
var borderColor: UIColor {
set {
self.layer.borderColor = newValue.cgColor
}
get {
return UIColor.init(cgColor: self.layer.borderColor!)
}
}
}
IB 文件的加載過程
Bundle 和 UINib
- 將 nib 加載到內(nèi)存
- 解固化并實例化 nib 文件里對應(yīng)的對象
- 建立 connections (outlet蕉堰、action)
- 調(diào)用 awakeFromNib() 方法
- 將 nib 中可見的控件顯示出來
本地化
兩種策略
- App 本地化跟隨系統(tǒng)語言
- App 內(nèi)部有一個可以設(shè)置語言的選項
本地化介紹
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 點擊 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 點擊 localize...,替換 zh-Hans.lproj 中的資源文件轻腺。
App 內(nèi)設(shè)置語言的本地化
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)臃腫、可維護性差的 VC贬养。
使用 Object
- 通常 VC 會成為很多對象的 delegate挤土,需要處理很多回調(diào)。用 Object 替 VC 實現(xiàn) delegate煤蚌。
- 將一些能用需求或交互模塊化在對應(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
可以不運行程序的情況下把源文件中的一些代碼實時渲染到 IB 中尉桩,但是源文件必須是 UIView 或者 NSView 的子類筒占。
prepareForInterfaceBuild()
只需要將實時渲染的代碼放到 prepareForInterfaceBuild() 方法中就可以了,該方法并不會在程序運行時調(diào)用蜘犁。
@IBInspectable
用 @IBInspectable
修飾的屬性會顯示在 IB 的 Show the Attributes inspector翰苫。
extension UIView {
private struct AssociatedKeys {
static var name: String?
}
// 存儲屬性
@IBInspectable var name: String {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.name) as! String
}
set {
objc_setAssociatedObject(self, &AssociatedKeys.name, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
// 計算屬性
@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
+ =
查看各個 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
連線
小技巧
兩個窗口
使用吸管快速設(shè)置顏色
IB 中的復(fù)制與粘貼
command
+ c
command
+ v
利用 Media Library 快速設(shè)置圖片
IB 開發(fā)中遇到的一些小 bug
最好的做法就是重啟 Xcode。
- 無法連線
IB 文件是否與源文件關(guān)聯(lián)
- @IBAction 紅色提示
先在源文件中定義好方法埃唯,再從源文件 拖 到 IB 文件進行 連線