Xcode11 新特性之SegueAction

Xcode11發(fā)布后,我們一直在驚嘆SwiftUI的強(qiáng)大,卻忽略了storyboard的一些改進(jìn)。據(jù)我所知,AppleWWDC期間也沒(méi)有提到過(guò)segue自定義初始化(initializers)的修改。 我們可以從Xcode和iOS 13發(fā)行說(shuō)明中看到一些修改提示。

SegueAction

假設(shè)我們使用storyboards時(shí)通過(guò)Segue進(jìn)行頁(yè)面跳轉(zhuǎn)

001

左側(cè)的BookController有一個(gè)Button,點(diǎn)擊該按鈕時(shí),它會(huì)跳轉(zhuǎn)到右側(cè)的PreviewController,以顯示該Book的詳情。 使用storyboard,您可以通過(guò)從按鈕拖拽到目標(biāo)控制器創(chuàng)建segue實(shí)現(xiàn)頁(yè)面跳轉(zhuǎn)。

002

要完成segue的配置,必須在屬性檢查器( attributes inspector)中添加一個(gè)唯一標(biāo)識(shí)符(Identifier):

003

頁(yè)面跳轉(zhuǎn)時(shí),我們需要將Model數(shù)據(jù)(本例中的Book)從源傳遞到目標(biāo)ViewController。 在Xcode 10和iOS 12中,我們使用源視圖控制器中的 prepare(for:sender) 來(lái)實(shí)現(xiàn):

// BookController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
  guard let previewController = segue.destination as? PreviewController else {
    fatalError("Missing PreviewController")
  }
  previewController.book = book
}

注意,我們沒(méi)有創(chuàng)建目標(biāo)視圖控制器。 UIKit通過(guò)調(diào)用init(coder:) 方法來(lái)初始化新的ViewController。 我們可以配置數(shù)據(jù)并將其傳遞給controller,但只能在UIKit創(chuàng)建controller之后。

我的PreviewController有一個(gè)Book屬性,但它是可選的:

// PreviewController
import UIKit

final class PreviewController: UIViewController {
  @IBOutlet private var textView: UITextView!

  var book: Book?

  override func viewDidLoad() {
    super.viewDidLoad()
    title = book?.title
    textView.text = book?.preview
  }
}

book屬性是optional的,因?yàn)樵诔跏蓟?code>PreviewController期間無(wú)法設(shè)置它。我想在創(chuàng)建PreviewController時(shí)初始化book,并把book成員變量改成let不可修改的,怎么辦呢?

segue是UIKit在調(diào)用segue方法期間,創(chuàng)建目標(biāo)視圖控制器的一種方法。

從Xcode 11開(kāi)始,我們還有另一種將數(shù)據(jù)傳遞到目標(biāo)視圖控制器的方法。我們可以通過(guò)使用@IBSequeAction在源視圖控制器中標(biāo)記一個(gè)方法來(lái)創(chuàng)建segue action

@IBSegueAction
private func showPreview(coder: NSCoder, sender: Any?, segueIdentifier: String?)
    -> PreviewController? {
    return PreviewController(coder: coder, book: book)
}

SegueAction方法具有三個(gè)參數(shù)。 必需的NSCoder參數(shù)以及可選的sender和segueIdentifier。 如果不需要,我們可以省略可選參數(shù):

@IBSegueAction
private func showPreview(coder: NSCoder)
    -> PreviewController? {
    return PreviewController(coder: coder, book: book)
}

如果該方法返回nil,則UIKit將調(diào)用 init(coder:) 方法來(lái)創(chuàng)建視圖控制器。 SegueAction不會(huì)阻止segue的發(fā)生。 無(wú)論哪種方式,都會(huì)在segue對(duì)象中傳遞新創(chuàng)建的視圖控制器給prepare(for:sender)方法。 由于這里我們不再需要它,因此從視圖控制器中刪除了prepare(for:sender)方法。

注意,Swift 5.1允許我們省略具有單個(gè)表達(dá)式的方法的return語(yǔ)句,我們進(jìn)一步簡(jiǎn)化SegueAction方法:

@IBSegueAction
private func showPreview(coder: NSCoder)
    -> PreviewController? {
    PreviewController(coder: coder, book: book)
}

要將storyboard中的segue連接到ViewController中的SegueAction方法,請(qǐng)從segue對(duì)象向view controller拖動(dòng),然后選擇SegueAction方法:

004
0041

如果正確連接了SegueAction,在segue的屬性檢查器(attributes inspector)中會(huì)看到該方法的Selector變成了你定義的方法:

005

現(xiàn)在,PreviewController可以創(chuàng)建一個(gè)自定義initializer方法,該初始化方法在創(chuàng)建視圖控制器時(shí) 用我們SegueAction傳遞的Book做參數(shù):

// PreviewController
import UIKit
final class PreviewController: UIViewController {
  @IBOutlet private var textView: UITextView!

  let book: Book

  init?(coder: NSCoder, book: Book) {
    self.book = book
    super.init(coder: coder)
  }

  required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  override func viewDidLoad() {
    super.viewDidLoad()
    title = book.title
    textView.text = book.preview
  }
}

自定義初始化方法必須調(diào)用super.init(coder:) 并傳遞從SegueAction中接收到的coder參數(shù)。 另外,Book屬性不再是可選的,因此我們可以將其從var更改為let。

自定時(shí)實(shí)例化方法

頁(yè)面跳轉(zhuǎn)時(shí)我們可以使用storyboards而不必使用segue。 例如,我可以將按鈕連接到視圖控制器中的IBAction方法,而不是創(chuàng)建segue:

@IBAction private func showPreview(_ sender: UIButton) {
  guard let previewController = storyboard?.instantiateViewController(
      withIdentifier: "PreviewController") as? PreviewController else {
      fatalError("Unable to create PreviewController")
    }
    previewController.book = book
    show(previewController, sender: self)
}

showPreview方法從storyboard中實(shí)例化視圖控制器,進(jìn)行配置然后顯示。 這種方式存在與segue相同的一些問(wèn)題。 我們調(diào)用storyboard的instantiateViewController(withIdentifier:)方法,然后返回一個(gè)ViewController。 任何Model數(shù)據(jù)(例如我們的Book)都必須是目標(biāo)視圖控制器的可選屬性。

蘋(píng)果在iOS 13發(fā)行說(shuō)明中對(duì)這個(gè)問(wèn)題進(jìn)行了完善:

You can now invoke a custom initializer from a creation block that’s passed through instantiateInitialViewController(creator:) or instantiateViewController(identifier:creator:).

例如,使用我們的自定義初始化方法傳遞Model數(shù)據(jù):

@IBAction private func showPreview(_ sender: UIButton) {
  guard let previewController = storyboard?.instantiateViewController(
        identifier: "PreviewController", 
        creator: { coder in
        PreviewController(coder: coder, book: self.book)
      }) else {
      fatalError("Unable to create PreviewController")
    }
    show(previewController, sender: self)
}

storyboard.instantiateViewController多了一個(gè)creator參數(shù),creator block提供了我們需要傳遞給自定義view controller初始化方法的coder,現(xiàn)在就把book可選參數(shù)改成let了。

遲到總比沒(méi)有好

未來(lái)可能是SwiftUI一統(tǒng)天下,但我仍然很高興storyboard有所完善。 第一個(gè)改動(dòng)使用Xcode11開(kāi)發(fā)低版本iOS也可以使用,第二個(gè)改動(dòng)就需要iOS13及以上版本支持了。 所以也許這就是遲到總比沒(méi)有好。

作者:kharrison
翻譯整理:樂(lè)Coding
原文地址:https://useyourloaf.com/blog/better-storyboards-with-xcode-11/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容