Swift版本 4.0ios
Xcode版本 9.2git
這周原本我是想要寫其餘知識的,但在構建 Demo
工程的時候, 我不由自主的就使用了 Storyboard
(下面簡稱 SB
),或者說是 Interface Builder
(下面簡稱 IB
),因此就想着寫一篇相關文章。github
這裏不討論使用這種方式的好壞,你們仁者見仁,智者見智,貓神的文章連接在後記裏面,個人觀點和他一致。swift
這部分針對徹底沒用過
SB
的讀者,極其基礎,熟悉的直接跳過!安全
下面是我這一小節須要完成目標的樣子,一個遊戲的展現界面和添加遊戲。app
先創建一個 Demo
項目,點擊 Main.storyboard
出現以下界面:框架
首先須要瞭解 SB
中幾個重要的區域,這裏是按照個人理解取的名字,只是簡單說明區域的做用,後面會詳細使用這幾個區域,如上圖所示:ide
Segue
及控制器上面的控件和佈局等等信息都在這裏顯示。SB
和代碼文件關聯和查看關聯後的信息,也能夠直接在這裏配置控件的屬性等等。細心的讀者可能會發現,區域1中,控制器在一個 scene
的下面,在 SB
中,scene
就對應着一個控制器。區域2裏面還有一個灰色箭頭,它表明這個控制器是當前 SB
文件的入口,會在後面詳細的講解。佈局
直接從控件區域拖拽了一個 UIView
控件到控制器上,而後在 Attributes inspector區域
(點擊配置區域中那個楔子形狀的按鈕)直接配置背景顏色爲灰色。若是顯示菜單欄中沒有沒有你想要的顏色,點擊 other
,裏面有多種方式配置顏色,如 RGB
和16進制顏色等等。學習
上圖這個區域裏還有一些其餘的屬性能夠配置,例如 UILabel
控件字體和字體顏色等等屬性等,就不深刻去展開了。
讀者確定注意到,控件在拖拽中,控制器出現了輔助虛線,能夠提醒你相對其餘視圖的位置信息,圖中所示的其中一條就是父視圖的中線。
解釋一下上圖的自動佈局操做:
Align
按鈕,勾選 Horizontalliy in Container
和 Vertically in Container
,而後添加這兩個佈局,相對於父視圖水平和垂直居中Add New Constraints
按鈕,添加 Width
和 Height
約束,都爲200。到這裏佈局就完成了,由於大小和位置都已經肯定。觀察上圖,我只添加了 Align
約束時,界面出現了紅線,這表明約束不完整。而且菜單欄上方出現帶有箭頭的小紅點,能夠點擊進去查看還有哪些約束沒有完成。這裏還有其餘的約束選項,讀者能夠自行嘗試。
Size inspector
區域 (點擊配置區域中那個小直尺按鈕) 雙擊寬度約束,進入了詳情配置界面,這裏能夠對約束進行二次修改。點擊菜單欄的約束,一樣能夠進入這個界面。這裏還有另外一種方式進行自動佈局,如圖所示:
按住 Ctrl
,而後選中灰色 View
,移動鼠標會出現一條線,拖到你想要相對其佈局的控件,圖中選擇的是父視圖,出現了一個菜單讓你選擇約束條件。一樣的操做也能直接在菜單欄中進行。甚至當控制器上控件比較多不容易選中時,能夠直接從控制器上拖到菜單欄上的控件上。
這一部分的操做是很簡單的,不過須要自動佈局的相關知識。
選中菜單欄的 View Controller Scene
,而後點擊鍵盤上的 delete
鍵,刪除咱們鼓搗的控制器。
從控件區域拖拽一個 UITabBarController
到工做展現區域:
在工做展現空白區域,雙擊鼠標左鍵和單點鼠標右鍵,能夠放大,縮小顯示內容。
如圖,UITabBarController
(它和 UINavigationController
都是容器控制器) 會自帶兩個子控制器,而且有兩個箭頭從 TabBarController
指向它們,這個箭頭的術語叫作 Segue
, 這裏的是 Relationship Segue
,表明控制器之間的關係。
刪掉 item1
的控制器,拖拽一個 UITableViewController
出來,而後讓它成爲 UINavigationController
的子控制器, 再讓 UINavigationController
成爲 UITabBarController
控制器的子控制器,操做如圖所示:
固然你也能夠直接拖拽一個 UINavigationController
出來,而後按住 Control
拖動選擇 view controllers
。不過我以爲點擊 Editor
這種方式更加便捷。
讓咱們的關注點來到 UITableViewController
,看到界面上有一個 Prototype Cells
,能夠理解爲咱們平時使用的那種 Cell
, 與之對應的是 Static Cells
, 從名字就能夠看出來,這種是靜態的,不可以循環使用,而且只能在UITableViewController
上使用。
紅框中,和咱們代碼實現中官方提供的4種 Cell
同樣,不過這裏咱們須要自定義,下面是完成後的樣子。
可能對沒有接觸過 IB
的讀者來講,這裏仍是比較麻煩,因此詳細描述一下。
選中 Cell
在右上角 Size inspector
區域修改 Cell
的高度爲120,這裏的高度設置只是方便咱們進行佈局,並非實際顯示的高度。
拖動一個 UIImageView
控件到 Cell
裏面,進行佈局。
iOS8
之後更新了讓Cell
本身自適應高度的新特性,因此這裏咱們不光要肯定本身的位置和大小,還須要將本身的大小反饋給Cell
讓其自適應高度,後面詳細使用。
相對於父視圖:
距離右邊20,距離上邊10,寬高100,這就已經肯定了位置和大小,不過爲了讓 Cell
知道咱們的高度,還須要設置一個距離底部的距離。這樣 Cell
就知道顯示的時候須要的高度。結合咱們目標的樣子,底部距離的設置是有個小問題的,後面來糾正。
繼續拖動一個 UILable
到 Cell
裏。
相對於父視圖: 距離左邊15,上邊10
相對於 UIImageView
: 距離它的左邊10
而後比較麻煩的地方來了。再拖動一個 UILable
到 Cell
裏。
相對於父視圖: 距離左邊15,距離底部10。
相對於 Game Name Label
:距離其底部10。
相對於 UIImageView
: 距離它的左邊10。
按照邏輯來講沒問題呀,由於上下左右都給了約束,是什麼緣由呢?
咱們點擊紅框中的小紅點進行查看:
UILabel
和UIButton
等控件有一個特色,它會根據內容自適應本身的大小。
如圖所示兩個 Label
在反饋大小給 Cell
時,Cell
也一樣會反饋本身的大小給兩個 Label
,這就會產生兩個問題:
1.若是 Cell
高度比內容反饋須要的高度大的時候,須要拉伸哪一個部分的內容?
2.若是 Cell
高度比內容反饋須要的高度小的時候,須要壓縮哪一個部份內容?
這裏就須要談到 AutoLayout
中的 Content Hugging
和 Content Compression Resistance
。
Content Hugging Priority
: 對應上面的第1中狀況,這個屬性的值越高,就越不容易被拉伸。Content Compression Resistance
:對應上面的第2種狀況,這個屬性的值越高,就越不容易被壓縮。顯然上面報錯的緣由是 Cell
的高度比兩個 Label
的內容高度大了,屬於第一種狀況,咱們讓 Game Name Label
不拉伸, 增長它的 Content Hugging Priority
(默認值爲251)比另外一個 Label
大(增長到252)。
這個問題解決了,但新問題又出現了:
由於 Game Detail Label
被拉伸,致使了內容居中,這看上去怪怪的,之前看到有關於討論讓 Label
居上的問題。但這並非這裏的解決辦法。還記得前面說過能夠對約束進行二次編輯嗎?選中 Game Detail Label
的 Bottom
約束,能夠在菜單區域選擇或者在小直尺圖標區域裏面找到它進行雙擊,就來到以下界面:
這裏有個 Relation
選項,點看能夠看到:
沒錯咱們選擇讓這個約束大於或等於10:
看上去是完成了,回到最初添加 UIImageView
約束的時候,我說過有一個小問題,UIImageView
的約束強行的讓 Cell
的高度爲120了。當 Label
內容不少換行超過120的時候,就會出現上面的第2種狀況, Cell
高度不夠完整顯示內容,這顯然不是咱們想要的結果。因此修改 UIImageView
的 Bottom
約束也爲距離底部大於等於10,到這裏佈局就結束了,最後別忘了設置 identifier
:
爲了讓 SB
和代碼關聯起來,建立一個繼承自 UITableController
的 GameVC.swift
文件、繼承自 UITableViewCell
的 GameCell.swift
文件和數據模型 Game.swift
文件,而後依次選中 SB
中的文件關聯:
繼續將 SB
中的屬性和代碼關聯起來:
也能夠直接從控制器中選中控件並按住 Control
進行拖動連線,這裏就再也不舉例了,這裏不只僅只能屬性連線,例如 UIButton
能夠直接連線一個點擊響應方法等等。連線後能夠在 Connections inspectors
(圓圈包含一個箭頭的按鈕) 查看:
注意: 一個控件屬性關聯屢次或者其餘關聯錯誤會引起運行奔潰,這是新手最容易犯的問題,若是名字寫錯了,須要先取消上次的關聯,再從新關聯。
![]()
Game.swift
文件中:
struct Game {
let name: String
let detail: String
let pictureName: String
static func getData() -> [Game] {
return [
Game(name: "絕地求生",
detail: "神仙打架遊戲",
pictureName: "game_one"),
Game(name: "英雄聯盟",
detail: "《英雄聯盟》(簡稱LOL)是由美國拳頭遊戲(Riot Games)開發、中國大陸地區騰訊遊戲代理運營的英雄對戰MOBA競技網遊。遊戲裏擁有數百個個性英雄,並擁有排位系統、符文系統等特點養成系統。《英雄聯盟》還致力於推進全球電子競技的發展,除了聯動各賽區發展職業聯賽、打造電競體系以外,每一年還會舉辦「季中冠軍賽」「全球總決賽」「All Star全明星賽」三大世界級賽事,得到了億萬玩家的喜好,造成了本身獨有的電子競技文化",
pictureName: "game_two")]
}
}
複製代碼
GameVC.swift
文件中:
class GameVC: UITableViewController {
var games: [Game] = []
override func viewDidLoad() {
super.viewDidLoad()
games = Game.getData()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return games.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "GameCell", for: indexPath) as! GameCell
cell.game = games[indexPath.row]
return cell
}
}
複製代碼
GameCell.swift
文件中:
class GameCell: UITableViewCell {
var game: Game! {
didSet {
gameNameLabel.text = game.name
gameDetailLabel.text = game.detail
gameImageView.image = UIImage(named: game.pictureName)
}
}
@IBOutlet weak var gameImageView: UIImageView!
@IBOutlet weak var gameNameLabel: UILabel!
@IBOutlet weak var gameDetailLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
}
}
複製代碼
準備工做完畢,運行 Demo
:
出錯了,細心的讀者確定早就發現這個問題了,那就是前面說的那個入口箭頭:
配置完畢後再運行:
先給控制器增長一個 title
:
而後把 Game
控制器拖到 UITabBarController
第一個位置:
繼續添加一個 UIBarButtonItem
, 並設置風格爲 Add
:
添加一個 UITableViewController
並讓其成爲 UINavigationController
的子控制器 ,按住Control
點擊 Add Item
,拖動到新的控制器上,會出現彈窗選擇跳轉方式,這裏選擇 Present Modally
,對應着咱們代碼中 Present
那個方法。
這裏的 Show
表明,若是是 UINavigationController
的子控制器就會執行 Push
方法,不是就會執行 Present
方法。
兩個控制器之間多出了一個帶箭頭的連線,這能夠理解爲界面切換 Segue
,用來描述控制器之間的跳轉,一個界面切換 Segue
只能單向跳轉。
設置新控制器的 title
爲 Game Add
,左邊添加一個 Cancle Item
, 右邊添加一個 Done Item
。而後繼續在 GameVC.Swift
的底部添加分類。
// MARK: - IBActions
extension GameVC {
@IBAction func cancelToGameVC(_ segue: UIStoryboardSegue) {
}
@IBAction func saveGameDetail(_ segue: UIStoryboardSegue) {
}
}
複製代碼
這是 unwind Segue
,用來返回到目標控制器。直接上圖:
選中 Game Add
中的 TableView
.接下來我直接用 static cell
進行佈局,
content
中選擇 Static Cells
,Style
中選擇 Grouped
。Section
中的 Cell
刪除到只剩一個,設置 Cell
的 Selection
爲 None
, 直接複製 Section
,這樣就有兩個含有一個 Cell
的 Section
。Section
設置標題( SB
中的 Header
)爲 Game Name
和 Game Detail
。Section
高度設爲50,第二個設爲200。拖一個 UITextField
到第一個 Cell
,佈局上0底0左10右10,拖一個 UITextView
到第二個 Cell
,佈局上底左右都是10。UITableViewController
的 GameAddVC.swift
文件,而後將裏面方法刪除到只剩 viewDidLoad
, 並關聯這個 SB
。UITextField
和 UITextView
連線到 GameAddVC.swift
文件中生成 @IBOutlet
屬性。@IBOutlet weak var gameNameTextField: UITextField!
@IBOutlet weak var gameDetailTextView: UITextView!
複製代碼
這裏之因此能直接將
Cell
中的屬性直接連線到控制器中,是由於靜態Cell
不會重用。
配置完成以下:
這裏省略了添加圖片的步驟,直接設定一個默認圖片。
選中剛纔 Done Item
添加的 Segue
,而後設置它的 Identifier
爲 AddGameDetail
。
在 AddGameVC.swift
中重寫父類方法並添加代碼:
var game: Game?
// 這個方法點擊 `Done` 的時候會調用
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "GameAddDetail",
let name = gameNameTextField.text {
game = Game(name: name,
detail: gameDetailTextView.text!,
pictureName: "game_default")
}
}
複製代碼
GameVC.swift
中,添加以下代碼:// 這個方法前面有,只是添加方法內的內容
@IBAction func saveGameDetail(_ segue: UIStoryboardSegue) {
guard let gameAddVC = segue.source as? GameAddVC,
let game = gameAddVC.game else {
return
}
games.append(game)
let indexPath = IndexPath(row: games.count - 1, section: 0)
tableView.insertRows(at: [indexPath], with: .automatic)
}
複製代碼
運行 Demo
, 以下:
在 SB
中,若是多我的同時對一個地方(例如同一個控制器)進行修改,很容易形成 Git
衝突,這也是反對者們反對使用 SB
的一個理由。不過在蘋果增長 Storyboard Reference
功能後,這種狀況在開發中徹底能夠避免了。
Demo
中的控制器數量較少,但在實際項目中,若是多我的都都只操做這個 Main.storyboard
,那將是一件很恐怖的事情。以前沒有 Storyboard Reference
功能時,多個 SB
之間的跳轉只能使用代碼的方式實現。如今來看看 Storyboard Reference
吧。
Main.storyboard
的控制器,就像桌面用鼠標多選文件那樣。Game.storyboard
,選擇在哪一個文件夾下面建立,而後肯定。完成後,咱們能夠看到 Main.storyboard
中的控制器變成了一個了 Storyboard Reference
,其餘控制器移到咱們新建立的 Game.storyboard
中去了。多人開發時,各自操做本身的業務 SB
,就基本避免了 Git
衝突。
一樣,咱們也能夠先直接建立 SB
文件,而後再從控件區拖拽一個 Storyboard Reference
, 而後再讓它和咱們新建立的 SB
文件關聯。
到這裏這一下節就結束了,我自認爲是寫得比較囉嗦,不過這也是沒有辦法的選擇,這部分知識更多的是界面上的操做,若是不寫明白,不容易闡述清楚!
這裏的所謂高級用法,是我一廂情願認爲的。
若是你以前沒有見過這個東西,那麼你確定爲某些屬性沒有暴露在 IB
的設置面板中而困擾過。@IBInspectable
的用處很簡單,就是讓咱們自定義的屬性也能直接在 IB
中選擇,例如貓神的文章中的建議:
extension UILabel {
@IBInspectable var localizedKey: String? {
set {
guard let newValue = newValue else { return }
text = NSLocalizedString(newValue, comment: "")
}
get { return text }
}
}
extension UIButton {
@IBInspectable var localizedKey: String? {
set {
guard let newValue = newValue else { return }
setTitle(NSLocalizedString(newValue, comment: ""), for: .normal)
}
get { return titleLabel?.text }
}
}
extension UITextField {
@IBInspectable var localizedKey: String? {
set {
guard let newValue = newValue else { return }
placeholder = NSLocalizedString(newValue, comment: "")
}
get { return placeholder }
}
}
複製代碼
IB
中能夠直接設置:
image view
設置圓角(這裏能夠直接擴展 UIView
)@IBInspectable var cornerRadius: CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
layer.masksToBounds = newValue > 0
}
}
複製代碼
IB 中能夠直接設置:
僅僅使用 @IBInspectable
沒法將屬性的設置實時顯示出來,還須要另外一個關鍵字的幫助。
它可以將一些繪圖代碼和 UIView
及其子類的 @IBInspectable
屬性實時渲染到 IB
中。
@IBInspectable
使用,建立 UIView
子類 CustomView
。拖拽一個 UIView
到另外一個 Item
控制器上,佈局上下居中,款高200,而後將它們關聯。此時如圖所示:CustomView.swift
中添加代碼,注意 @IBDesignable
的位置:@IBDesignable
class CustomView: UIView {
@IBInspectable var cornerRadius: CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
layer.masksToBounds = newValue > 0
}
}
@IBInspectable var borderColor: UIColor = UIColor.white {
didSet {
layer.borderColor = borderColor.cgColor
}
}
@IBInspectable var borderWidth: Int = 1 {
didSet {
layer.borderWidth = CGFloat(borderWidth)
}
}
}
複製代碼
而後看效果:
Corner Radius
設爲0:override func draw(_ rect: CGRect) {
let path = UIBezierPath(ovalIn: rect)
UIColor.green.setFill()
path.fill()
}
複製代碼
結果如圖:
咱們都知道 Presnet
切換時系統默認的公開動畫有四種,若是咱們想自定義的話,須要建立一個 UIStoryboardSegue
的子類。
class CustomAnimationPresentationSegue: UIStoryboardSegue, , UIViewControllerTransitioningDelegate, UIViewControllerAnimatedTransitioning {
override func perform() {
destination.transitioningDelegate = self
super.perform()
}
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return self
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return self
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.5
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
let toView = transitionContext.view(forKey: UITransitionContextViewKey.to)!
if transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) == destination {
// Presenting.
UIView.performWithoutAnimation {
toView.alpha = 0
containerView.addSubview(toView)
}
let transitionContextDuration = transitionDuration(using: transitionContext)
UIView.animate(withDuration: transitionContextDuration, animations: {
toView.alpha = 1
}, completion: { success in
transitionContext.completeTransition(success)
})
}
else {
// Dismissing.
let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from)!
UIView.performWithoutAnimation {
containerView.insertSubview(toView, belowSubview: fromView)
}
let transitionContextDuration = transitionDuration(using: transitionContext)
UIView.animate(withDuration: transitionContextDuration, animations: {
fromView.alpha = 0
}, completion: { success in
transitionContext.completeTransition(success)
})
}
}
}
複製代碼
自定義了一個簡單的漸隱動畫,這裏關於自定義的跳轉動畫的部分我不想仔細探討(排在我想寫內容的隊列總)。而後咱們在 IB
關聯跳轉到添加遊戲的 Segue
和 Cancle&Done
的 unwind Segue
爲 CustomAnimationPresentationSegue
。 演示效果:
其實這不算是 IB
的高級使用,它可以掃描整個項目中的資源文件(好比圖片名,View Controller
和 segue
的 identifier
等),並生成一種類型安全的獲取方式。
let icon = UIImage(named: "settings-icon")
let viewController = UIStoryboard(name: "Main",
bundle: nil).instantiateViewController(withIdentifier: "myViewController") as! MyViewController
複製代碼
let icon = R.image.settingsIcon()
let viewController = R.storyboard.main.myViewController()
複製代碼
關於 IB
的操做目前我知道的就這些,若是你有更好的使用技巧能夠評論分享討論一下。
最近我撿起了個人微博,由於不少 iOS
界的前輩都喜歡微博分享技術,我也關注了不少,收益匪淺。例如這個 OC
項目 ZHNCosmos Github地址,代碼工整,邏輯清晰,我這個菜鳥準備好好學習一下。
另外附上個人微博,我天天都會轉發一些大佬的技術動態,請你們隨緣關注:
參考文章
WWDC2015視頻自帶中文字幕 What's New in Storyboards