創建一個自訂segue不是困難的事,因為它包含了 iOS 的標準程式技術。實際上所須要的是,做為 UIStoryboardSegue 類別的子類別(subclass),並且只要覆寫(override)一個稱做 perform的方法。動畫邏輯必須要在這個方法中實做。觸發視圖控制器間的轉換,必須由開發者以程式來執行,但這些都是標準程序。 這篇教學的目標是要讓你知道如何實做自訂segue,透過簡單可是能夠切中要點的範例App 來全方面展現這個觀念。瞭解如何自訂 segue 能夠驅動你開發更強大的應用程式。除此以外,這也能夠讓你開發出使用者體驗最佳化,還有漂亮吸晴的應用程式。swift
當iOS 第五版(iOS 5)釋出後,它針對使用者介面的設計導入了一個全新且革命性的方式,也就是storyboard的採用,這也表示了現行的設計邏輯要從頭改變。在iOS 5之前,每一個控制器(controller)幾乎都跟著一個介面建構器(Interface Builder)檔,也就是所謂的nib
或xib
檔,其原理很簡單:每一個視圖控制器假設都會設計一個相對應的 nib 檔,而全部的 nib 檔的組合就是應用程式的所有介面。在某種情況,這種開發方式很方便,因為開發者在設計該功能時只要將重點放在該介面便可,可是另外一面卻也會所以產生許多的檔案,甚至開發者無法檢視整個App介面的開發全貌。框架
有了 Storyboard 之後,全部的一切都成為歷史,因為這個方法已經幾乎被全部的開發社羣所使用。相對於舊的技術,Storyboard提供至少了三種顯著的好處:ide
場景
)間的轉換(或者稱做過場,transition),以及它們之間所進行的全部事情,現在都能清楚的定義以及清楚地呈現給開發者。從以上的說明,場景(scene)間的轉換構成了Storyboard特別的一章,也就是所謂的 segue.工具
Segue 跟應用程式導覽(navigation)的處理很密切,因為實際上它是定義了從一個視圖控制器至另外一個的轉換細節。這些細節定義了動畫是否有被使用。或者該使用哪種動畫,當然還有,實際轉換時的預備與成效。可是不僅如此,segue 也能夠用來傳遞資料至準備要顯示的視圖控制器上,而這幾乎是全部開發者常會碰到的狀況。ui
從程式觀點來看,一個 segue 是 UIStoryboardSegue
類別的物件,在iOS 5第一次導入時就已經有說明。不像其餘類別的物件,這樣的物件不能直接創建使用。不過Segue 的屬性能夠被指定,所以在應用程式運行中,Segue準備要執行時,它們會被運用到。UIKit框架提供了一些預設的 segue 加上預設的動畫轉換,對於簡單的案例已經很適合了。其中包括了push segue (好比App中導覽控制器的呈現)、 modal segue 加上動畫功能,以及 popover segue。對於一些更進階的需求,iOS SDK 的預設 segue 可能就不夠用了,因此開發者必須要自訂segue(custom segue)。spa
創建一個自訂segue不是困難的事,因為它包含了 iOS 的標準程式技術。實際上所須要的是,做為UIStoryboardSegue
類別的子類別(subclass),並且只要覆寫(override)一個稱做 perform
的方法。動畫邏輯必須要在這個方法中實做。觸發視圖控制器間的轉換,必須由開發者以程式來執行,但這些都是標準程序。code
這篇教學的目標是要讓你知道如何實做自訂segue,透過簡單可是能夠切中要點的範例App 來全方面展現這個觀念。瞭解如何自訂 segue 能夠驅動你開發更強大的應用程式。除此以外,這也能夠讓你開發出使用者體驗最佳化,還有漂亮吸晴的應用程式。orm
因此,若是你有興趣學習我剛所敘述的部分,一塊兒來探索接下來有關自訂 segue 的細節與內容。ip
不像以前我以前幾篇為教學內容所提供的起始專案模板,在這裡我們要從頭來創建一個應用程式。我會這樣作的意義是因為我們內容的一些重要部分與介面建構器(Interface Builder)有關,因此我認為逐步詳細從頭來說明會更好。部署
如同我先前所述,我們準備開發一個十分簡單的程式,我們創建兩個自訂的segue。從頭說明好了,我們的範例 App 會有三種視圖控制器,也就是在介面建構器有三個介面,以及三個相對的類別。預設第一個是由 Xcode 所創建,因此我們只要加上另外兩個。我們要創建的自訂segue,是用來由第一個視圖控制器導覽至第二個(以及返回),並且從第一視圖控制器至第三個(然後再次返回)。我們將不會在第二及第三個視圖控制器間加入任何連結。
如同剛說過的,我們會創建兩個自訂的segue。對每一個,我們會創建兩個相對應的類別(所以,總共是四個):針對第一個,我們會實做全部執行 segue,從第一個視圖控制器至下一個所需的自訂邏輯,。第二個,我們會實做返回第一個視圖控制器的邏輯,或者換句話說,我們會實做 unwind segue
。我們待會會見到更多有關 unwind segue 的部分,現在只要記得這是用來讓我們返回到前一個視圖控制器的 segue。
這些視圖控制器自己沒作什麼。上面只有一個標示這個視圖控制器名稱的標籤,另外每一個都有不一樣的背景顏色,如此一來我們能夠容易地觀察轉換(transition,或者稱過場)過程(是的,這是一個色彩繽紛的App)。第一個與第二個視圖控制器會有多一個標籤,用來顯示從其餘視圖控制器傳遞過來的自訂訊息。
最後,當如下動做發生之後,Segue 將會執行:
上滑(Swipe up)
手勢,返回方向則使用下滑手勢(Swipe down)
。我不準備描述要實做的動畫,我會讓你親眼見到。UIButton
)。若是要返回,我們會使用上滑手勢。同樣的,我不說明任何有關動畫的事情。當然,我們準備在視圖控制器間傳遞資料。
往下繼續以前,如下是我們準備要示範的範例,它也呈現了我們自訂 segue 準備要運做的內容:
注意當你創建一個自訂 segue,你能夠實做任何你能夠想像獲得或者須要的動畫特效,你能夠嘗試一下待會我們接下來的內容。
我們打開 Xcode 來開始。選擇創建一個新專案,在接下來的導引中,選取 Single View Application 做為專案模板。點擊 Next 按鈕。在 Product Name: 欄,設定 CustomSegues 做為專案的名稱。另外要確認選取 Swift 做為程式語言,而 Device 設為 iPhone。
再次點擊 Next。在這個步驟,選擇儲存這個專案的空間,並點擊 Create 按鈕。
現在專案已經準備好了,在專案導覽器(Project Navigator)選擇相對的羣組。預設會打開 General標籤,而這正是我們所須要的。在 Device Orientation 區域的Deployment Info區塊,取消Landscape Left 與 Landscape Right 選項的勾選。只留下Portrait 選項的選取。另外,假若你想要在以前的做業系統測試你的App,你也能夠任意將部署目標(deployment target)從 8.1 變更至以前的 iOS 版本(可是最好不要回到iOS8以前的版本)。
現在,我們準備繼續接下來的實做,開始進行第一個介面的設定。
第一個步驟是加入我們準備在介面中用到的視圖控制器。因此,在專案導覽器點擊 Main.storyboard
檔,介面建構器便會出現。我們準備在這邊將我們的工做區分紅三個部分,針對每一個創建一個視圖控制器。可是首先,要確認有將目前的尺寸類別(size class)改為 Compact Width與Regular Height,這樣便會符合iPhone 的介面:
當你打開介面建構器,你會見到Xcode 加入的預設場景。我們從這邊開始進行。一開始,加入兩個UILabel
物件至場景中。第一個,設定如下的屬性:
View Controller #1
另外,設定 Top
、Leading
、Trailing
與 Height
約束條件(constraint):
第二個標籤,設定屬性以下
接下來,設定 Horizontal Center in Container
、 Vertical Center in Container
、Width
與Height
約束條件以下:
當你完成屬性的設置與兩個標籤的約束條件,加入一個 UIButton
物件到場景中。並作如下的指定:
對於它的約束條件,只要按下介面建構器右下方的Pin按鈕,並在選取Selected Views 區塊下的Add Missing Constraints。
完成後,選取視圖並在屬性檢閱器(Attributes Inspector)中打開相對應的下拉選單,並選取 Other…選項來變更其背景顏色。在顏色挑選器,點擊 Color Palettes* 按鈕,然後選取 Crayons** 色盤。挑選Cantaloupe 顏色,然後關閉顏色挑選器。
這是第一個場景的截圖:
是時候來設定第二個視圖控制器了。從元件庫中拖曳一個視圖控制器(view controller)元件,直接拉進畫面中。然後在這裏加入兩個 UILabel
物件,並指定跟上面一樣的屬性。也不要忘記設定約束條件。這裏惟一的差異是第一個標籤的文字,應該改為 "View Controller #2"
(不須要引號)。
接下來,再次選取視圖,並打開顏色挑選器。這一次挑選Honeydew 顏色(在列表的第二個),並關閉挑選器。
Xcode 會顯示出一個警告,告訴你兩個視圖控制器間沒有segue。先不要管它,我們待會會修復。
下一張圖列出專案的第二個場景:
最後,我們再從元件庫取出一個視圖控制器並加入畫面中。這裏,只要加上一個 UILabel
元件,而不是兩個。設定你在前兩個場景的第一標籤的屬性,可是有兩個不一樣的地方是:首先,設定標籤的Y原點為 389第二個,設定 "View Controller #3"
值做為標籤的文字。沒有其餘子視圖會加進去。因此你只要幫這個視圖挑選背景顏色便可。依照你已經作過兩次的步驟,最後一次打開顏色挑選器,並選取Sky 顏色。
依照其約束條件,設定 Horizontal Center in Container
、Vertical Center in Container
、Width
與Height
約束條件,如同你在其餘兩個視圖控制器的第二個標籤一樣。Y原點以及這些約束條件的變更會讓你將標籤在視圖中徹底置中。
如下是介面的第三個場景的樣貌:
對於我們前面在介面加入的場景,我們必須設定一個不一樣的類別,因此我們能夠實做每一個細節。預設每一個場景是設為 ViewController
,這能夠跟我們第一個視圖控制器(自動由Xcode產生的)完美搭配。不過對於其餘兩個,我們必須要創建兩個新類別,因此我們繼續往下。
第一個步驟是創建一個新類別檔,因此至 File > New > File… 選單來開始這個程序。接下還會出現創建新檔案的導引,在第一步,確認你有在iOS區塊的Source 分類選取Cocoa Touch Class。
點擊 Next 選單。在導引的第二個步驟,在Subclass of: 欄位選取或輸入UIViewController 值。然後,指定SecondViewController 做為你所創建檔案的名稱。當然,你不能變更程式語言,因此確認最後一個欄位,Swift 有被選取。
再一次點擊 Next,然後是點擊Create來創建新檔案。完成之後,你會在專案導覽器見到一個稱做SecondViewController.swift
的新檔案。
再次重複以上的步驟,並幫專案的第三個視圖控制器加入類別檔。並將這個類別檔案設為ThirdViewController,其餘的步驟保持一樣。
在你加入第二個檔案之後,你會在專案導覽器見到它們,以下所示:
現在,在我們回到介面建構器並設定相對應的類別給場景以前,我們來宣告一對待會會用到的IBoutlet屬性。至 ViewController.swift
檔,加入如下的屬性:
1 |
@IBOutlet weak var lblMessage: UILabel! |
現在至SecondViewController.swift
檔,並加入如上面同一行程式。這兩個屬性會與前兩個控制器的第二個標籤作連結,這樣我們之後能夠在上面顯示訊息。
再次打開Main.storyboard
檔,並選取第二個場景(綠色背景)。在上面,點擊視圖控制器物件,然後至工具面板的識別檢閱器(Identity Inspector)處,在Custom Class 區塊的 Class 欄,設定為我這部分加入的第一個類別至專案中,也就是 SecondViewController
:
針對第三個場景(有淡藍色背景)重作這個步驟。選取之後,點擊場景上方的視圖控制器,然後設定為ThirdViewController
做為其類別:
很棒,現在每一個場景能夠符合不一樣的類別。在我們完成這部分以前,我們先連結兩個我們宣告的IBoutlet屬性,我們從第一個視圖控制器開始(橘色這個)。點擊畫面上方的視圖控制器物件,然後按下Ctrl鍵不放,拖曳至場景中心的標籤:
在跳出的小視窗,選取lblMessage
屬性,連結便會成功完成。
接著在Second View Controller
場景設定相同的步驟,並連結視圖控制器的 lblMessage
屬性至場景中間的標籤。
自訂segue的創建包含了UIStoryboardSegue
的子類別,以及一個強制實做的方法,稱做perform
。在這個方法中,在兩個視圖控制器間執行轉換的自訂邏輯會實際應用到,因此在我們撰寫任何segue相關的程式前,讓我們加入全部缺乏的類別。
在專案加入一個新類別的方式,已經在前面的部分有作了細項說明,因此用它做為你要在這邊加入所需檔案的參考。在我給你每一個檔案的細節前,我先說明我們總共會創建四個檔案。那是因為每一個自訂 segue 都須要兩個類別,我們準備創建這兩個。重點是,第一個類別是用來實做實際執行segue時其全部的邏輯以及轉換。第二個類別是用來實做當第二個視圖轉換回第一個視圖控制器的相反動做。這也是所謂的返回(unwind)segue,我們待會會見到更多的細節。
有了這些概念後,開始加入新類別檔案至專案中。加入每一個檔案時需確保兩件事:要選取Cocoa Touch Class 模板,在導引過程第二步驟並設定UIStoryboardSegue 作為Subclass of: 欄位的值。
全部四個檔案的名稱以下:
FirstCustomSegue
FirstCustomSegueUnwind
SecondCustomSegue
SecondCustomSegueUnwind
加入專案後,以上這些都在專案導覽器內能夠找到。
我們開始進行第一個客製化segue,在撰寫任何一行程式以前,為了要應用自訂轉換,須要在介面建構器的兩個場景間加上segue。在這裏,我們準備在View Controller
場景(橘色背景)與 Second View Controller
場景( 綠色背景)間創建segue。
同樣地,打開Main.storyboard
檔。為了方便創建segue,若是文件大綱(Document Outline)隱藏起來,展開它,並在View Controller Scene
區塊選取View Controller
物件:
然後,按住Ctrl鍵,拖曳至Second View Controller Scene
區塊下的Second View Controller
一個黑色視窗跳出,列著各種有關新 segue 的選項。在這裡面有一個稱做 custom的名稱:
點擊它之後,segue 便會馬上創建。你能夠檢查畫面中的兩個場景是否有連上一條線,確認 segue 是否有創建。
在我們開始寫程式以前,還須要確定幾個設定。首先,在畫面中點擊 segue 線,並在 Utilities 面板打開屬性檢閱器。其中一件最重要的任務是設定 segue 的識別碼(identifier),我們會在程式中參照它。重要的是對於不一樣的segue不要指定相同的識別碼。在這裏,設定idFirstSegue 做為 我們 segue 的識別碼。另外在 Segue Class 欄位,我們必須指定用來做為自動轉換的自訂類別名稱。在這裏,設定FirstCustomSegue 做為自訂 segue 的類別。執行期間,這個 App 將會執行這個類別的程式, 讓 segue 能夠順利運做。
現在新自訂的 segue 已經出現,且作了指定,我們來實做當第一個控制器導覽至第二個時的轉換。簡單地說,一般跟自訂 segue 有關的程式是去「玩」兩個視圖控制器的視圖。首先,指定目標視圖控制器視圖(destination view controller)的初始狀態,而它是手動加入App的視窗。然後當來源視圖控制器((source view controller))的視圖以離開畫面後,以動畫將目標視圖放置於所想位置。
註:一個segue物件參照準備要呈現的視圖控制器,就是 目標視圖控制器,而目前的視圖控制器就是所謂的來源控制器。
最後,當動畫轉換結束後,新視圖控制器便直接呈現給使用者看,沒有進一步的動畫。
在這篇教學中整個App所要達成的特效是,無論segue是如何執行,我們想要第一個視圖控制器的視圖向上滑,而第二個視圖控制器的視圖跟著移動。兩者視圖間沒有空間,第二個將會「貼」至第一個視圖。
我們來開始寫程式,逐步來瞭解,最後的地方,我會提供整個方法的實做給你參考。
打開FirstCustomSegue.swift
檔,然後定義如下的函數:
1 2 3 |
override func perform() {
} |
因為這個函數已經定義在UIStoryboardSegue
父類別(superclass),在這裏我只是覆寫它,因此我們能夠加入我們想要的自訂邏輯,所以,第一步是指定來源與目標視圖控制器的視圖給兩個區域變數,以利後面的工做。
1 2 3 4 5 6 |
override func perform() { // 指定來源與目標視圖給區域變數 var firstVCView = self.sourceViewController.view as UIView! var secondVCView = self.destinationViewController.view as UIView!
} |
另外,我們須要畫面的寬度與高度值,因此讓我們以兩個變數來存放:
1 2 3 4 5 6 7 8 |
override func perform() { ...
// 取得畫面寬度與高度 let screenWidth = UIScreen.mainScreen().bounds.size.width let screenHeight = UIScreen.mainScreen().bounds.size.height
} |
好的,我們來指定視圖要出現的初始位置。想像我們想要將視圖從底部移到頂部,我們會將視圖移至畫面的可視範圍外,置於目前視圖的下方。這很容易辦到,只要設定視圖的框(frame)便可:
1 2 3 4 5 6 7 |
override func perform() { ...
// 指定目標視圖的初始位置 secondVCView.frame = CGRectMake(0.0, screenHeight, screenWidth, screenHeight)
} |
這裏,第二個視圖控制器還不是App window 的子視圖。因此在我們實做實際的動畫前,很清楚地我們必須將其加入 window 中。這可使用 App 的 window 物件,insertSubview(view:aboveSubview:)
方法來辦到。如同如下,首先我們存取 window 物件,然後我們加入目標視圖:
1 2 3 4 5 6 7 8 |
override func perform() { ...
// 取得App的 key window 並插入目標視圖至目前視圖(來源視圖)上 let window = UIApplication.sharedApplication().keyWindow window?.insertSubview(secondVCView, aboveSubview: firstVCView)
} |
對於above
這個字不用感到困惑。有關視窗子視圖的順序,它們全都是以堆疊方式來置放,畢竟這樣纔不會產生麻煩。
最後,我們來產生轉換動畫。首先,我們會將第一個視圖控制器的視圖從最上面移出畫面,同時我們會將第二個視圖移至第一個原來的位置。實際上我們說「移」,意思是指修改兩個視圖的框架(frame):
1 2 3 4 5 6 7 8 9 10 11 12 13 |
override func perform() { ...
// 轉換動畫 UIView.animateWithDuration(0.4, animations: { () -> Void in firstVCView.frame = CGRectOffset(firstVCView.frame, 0.0, -screenHeight) secondVCView.frame = CGRectOffset(secondVCView.frame, 0.0, -screenHeight)
}) { (Finished) -> Void in
}
} |
以上這兩行會執行所需的效果。不過我們還沒完成,因為我們還沒有呈現新的視圖控制器(目標視圖控制器)。我們會在第二個閉包(closure)實做它,以下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
override func perform() { ...
// 轉換動畫 UIView.animateWithDuration(0.4, animations: { () -> Void in firstVCView.frame = CGRectOffset(firstVCView.frame, 0.0, -screenHeight) secondVCView.frame = CGRectOffset(secondVCView.frame, 0.0, -screenHeight)
}) { (Finished) -> Void in self.sourceViewController.presentViewController(self.destinationViewController as UIViewController, animated: false, completion: nil) }
} |
就這樣,整個上面函數的全貌以下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
override func perform() { // 指定來源與目標視圖給區域變數 var firstVCView = self.sourceViewController.view as UIView! var secondVCView = self.destinationViewController.view as UIView!
// 取得畫面寬度及高度 let screenWidth = UIScreen.mainScreen().bounds.size.width let screenHeight = UIScreen.mainScreen().bounds.size.height
// 指定目標視圖的初始位置 secondVCView.frame = CGRectMake(0.0, screenHeight, screenWidth, screenHeight)
// 存取App的 key window 並插入目標視圖至目前視圖(來源視圖)上 let window = UIApplication.sharedApplication().keyWindow window?.insertSubview(secondVCView, aboveSubview: firstVCView)
// 轉換動畫 UIView.animateWithDuration(0.4, animations: { () -> Void in firstVCView.frame = CGRectOffset(firstVCView.frame, 0.0, -screenHeight) secondVCView.frame = CGRectOffset(secondVCView.frame, 0.0, -screenHeight)
}) { (Finished) -> Void in self.sourceViewController.presentViewController(self.destinationViewController as UIViewController, animated: false, completion: nil) }
} |
在你第一次測試 App 前,我們也必須讓自訂 segue 能夠執行。這會在 ViewController
視圖控制器的視圖上執行向上滑動手勢時發生。所以,繼續打開 ViewController.swift
檔。至viewDidLoad
方法來創建一個新手勢物件,並加入至視圖中:
1 2 3 4 5 6 7 |
override func viewDidLoad() { ...
var swipeGestureRecognizer: UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: "showSecondViewController") swipeGestureRecognizer.direction = UISwipeGestureRecognizerDirection.Up self.view.addGestureRecognizer(swipeGestureRecognizer) } |
當手勢執行時,showSecondViewController
函數,準備要被呼叫,可是還沒實做。所以讓我們它的定義,只有簡單一行:我們將執行自訂segue :
1 2 3 |
func showSecondViewController() { self.performSegueWithIdentifier("idFirstSegue", sender: self) } |
你能夠注意到,我們使用它的識別碼值存取segue。以上方法的呼叫讓我們在視圖上向上滑動時,FirstCustomSegue
類別的實做能夠被執行。
若是你想要的話,你能夠馬上測試這個App。你能夠在模擬器或真實的裝置執行,你會見到即便向上滑動時會發生轉換,可是你還是不能導覽回去第一個視圖控制器。這很正常,因為我們馬上要實做這個函數。
創建自訂 segue 時,必須要實做兩個方向的導覽:從來源至目標視圖控制器,以及返回。幾乎不可能只有創建單一方向的導覽。現在我們實做了segue,顯示了我們想要呈現第二個視圖控制器的動畫,我們必須要進行能夠反向的動做。執行返回導覽的segue,稱做unwind segue
。
為了簡化,一個 unwind segue 是… 就跟標準的一樣,可是設置上會有點複雜。須要執行幾個步驟,不過全部都是由標準程式技術所組成。注意兩件事:
perform
法,並且應用任意我們想要或App所需的自訂邏輯與動畫。不過,當導覽返回來源視圖控制器,不須要跟標準segue的轉換特效相同。unwind segue的實做是我們步驟中的一部分,主要是:
UIStoryboardSegue
類別的一個子類別中,透過perform
方法的覆寫來實做自訂邏輯。UIViewController
類別所提供特定方法中的定義,讓系統知道unwind segue準備要執行什麼。此時你必定感到一頭霧水,可是慢慢地你會瞭解一切的意思。
我們來定義上面第一個步驟的 IBAction 方法。這個動做方法,必定都是在我們想要導覽的初始視圖控制器中實做。如我所說,實做內容是能夠空白的,可是你必須要謹記在心,你至少要定義一個方法,否則便不能繼續。在App中是不須要有許多個這樣像 unwind segue 的動做方法。大部份只要定義一個像這樣的動做方法,並檢查哪一個 unwind segue 要呼叫,這樣就夠了。
在動做中,打開 ViewController.swift
檔。在其中加入如下的程式:
1 2 3 |
@IBAction func returnFromSegueActions(sender: UIStoryboardSegue){
} |
我們待會會加入一些範例程式。注意這個方法的參數是 UIStoryboardSegue
物件,這個細節很重要,須要記住。
現在至介面建構器,打開 Main.storyboard
檔。是時候創建unwind segue了,我們會由檢查Second View Controller場景內的物件來開始,不論是在場景自己中檢查或者在文件大綱中檢查均可以。假若你看得仔細點,你會發現有一個物件稱做Exit:
若是你從未創建過自訂segue,你可能沒用過它,也許想知道這是什麼,好的,這就是你要創建的unwind segue。
點擊場景中的Second View Controller
物件,然後按住Ctrl拖曳至Exit物件:
接著會出現以下的跳出選單,上面有我們前面定義的IBAction方法:
確認有選取它。unwind segue 便會創建,你就能夠在文件大綱中檢查,沒有創建新的線,可是Segue已經建好了:
現在點擊剛創建的unwind segue,並打開屬性檢閱器。在上面設定 ** idFirstSegueUnwind** 做為segue的識別碼:
unwind segue 創建過程的的第二部分已經結束。這表示是時候寫些程式了,讓unwind segue的能正常執行。
一開始,打開FirstCustomSegueUnwind.swift
檔。再一次,我們準備覆寫perform
方法,並使用區域變數來做為視圖的參照。另外,我們也使用另外一個變數來存放畫面的高度。
1 2 3 4 5 6 7 |
override func perform() { // 指定來源與目標視圖給區域變數 var secondVCView = self.sourceViewController.view as UIView! var firstVCView = self.destinationViewController.view as UIView!
let screenHeight = UIScreen.mainScreen().bounds.size.height } |
注意,此時的來源視圖控制器是 Second View Controller
,而目標則是 View Controller
.繼續往下,這裏我們不指定任何視圖的初始狀態,我們只會加入第一視圖控制器(要返回那個)的視圖至window的子視圖:
1 2 3 4 5 6 7 |
override func perform() { ...
let window = UIApplication.sharedApplication().keyWindow window?.insertSubview(firstVCView, aboveSubview: secondVCView)
} |
最後,我們必須要對兩個視圖的移動作動畫,這次它們同時朝底部移動:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
override func perform() { ...
// Animate the transition. UIView.animateWithDuration(0.4, animations: { () -> Void in firstVCView.frame = CGRectOffset(firstVCView.frame, 0.0, screenHeight) secondVCView.frame = CGRectOffset(secondVCView.frame, 0.0, screenHeight)
}) { (Finished) -> Void in
self.sourceViewController.dismissViewControllerAnimated(false, completion: nil) }
} |
透過兩個視圖y點(在Y軸變更偏移量),我們在預設的動畫時間內將他們放置於新位置。當轉換結束之後,我們只是將第二個視圖控制器解除,沒有加上任何其餘動畫,這樣就完成了。現在整個方法的實做全貌以下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
override func perform() { // Assign the source and destination views to local variables. var secondVCView = self.sourceViewController.view as UIView! var firstVCView = self.destinationViewController.view as UIView!
let screenHeight = UIScreen.mainScreen().bounds.size.height
let window = UIApplication.sharedApplication().keyWindow window?.insertSubview(firstVCView, aboveSubview: secondVCView)
// 轉換動畫 UIView.animateWithDuration(0.4, animations: { () -> Void in firstVCView.frame = CGRectOffset(firstVCView.frame, 0.0, screenHeight) secondVCView.frame = CGRectOffset(secondVCView.frame, 0.0, screenHeight)
}) { (Finished) -> Void in
self.sourceViewController.dismissViewControllerAnimated(false, completion: nil) } } |
至目前為止一切都蠻順利的。我們創建了一開始所需的IBAction方法,以及unwind segue,並實做了動畫轉換的自訂邏輯。最後有一個方法也須要實做,在這個方法我們會使用FirstCustomSegueUnwind
類別。
打開 ViewController.swift
檔,定義如下的方法:
1 2 3 |
override func segueForUnwindingToViewController(toViewController: UIViewController, fromViewController: UIViewController, identifier: String?) -> UIStoryboardSegue {
} |
這是UIViewController
類別所提供的方法。它在undwind Segue執行時會自動地被呼叫,而它的實做是強制性的。它有三個參數:
fromViewController
:這是目前所顯示且要解除的視圖控制器。toViewController
:這是目標視圖控制器,換句話說視我們想要顯示的控制器。identifier
:所要執行segue的識別碼。注意這個方法回傳了 segue物件。我們所要作的很是簡單:我們會使用識別碼來判斷要執行的segue,然後我們會初始化一個unwind segue 自訂類別的物件並回傳。另外,我們也會呼叫 super
方法,如同如下程式所示,我們來覆寫它:
1 2 3 4 5 6 7 8 9 10 11 12 |
override func segueForUnwindingToViewController(toViewController: UIViewController, fromViewController: UIViewController, identifier: String?) -> UIStoryboardSegue { if let id = identifier{ if id == "idFirstSegueUnwind" { let unwindSegue = FirstCustomSegueUnwind(identifier: id, source: fromViewController, destination: toViewController, performHandler: { () -> Void in
}) return unwindSegue } }
return super.segueForUnwindingToViewController(toViewController, fromViewController: fromViewController, identifier: identifier) } |
首先,我們確認識別碼參數確定有一個值,因此我們使用if let
陳述。然後使用idFirstSegueUnwind
做為我們要採用segue的確認值,並且在if
陳述中我們初始化FirstCustomSegueUnwind
類別的物件。這個物件會在 if
陳述中回傳,在陳述外面我們在呼叫完super
類別後,我們回傳了相同方法的值。
unwind segue現在已經準備好了。剩下的是實做手勢觸發時的內容。打開SecondViewController.swift
檔,直接至viewDidLoad
方法,在裡面加上下面幾行:
1 2 3 4 5 6 7 8 |
override func viewDidLoad() { ...
var swipeGestureRecognizer: UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: "showFirstViewController") swipeGestureRecognizer.direction = UISwipeGestureRecognizerDirection.Down self.view.addGestureRecognizer(swipeGestureRecognizer)
} |
我們定義在上面的手勢是向下滑,跟我們以前的正好相反。現在,我們定義showFirstViewController
動做方法:
1 2 3 |
func showFirstViewController() { self.performSegueWithIdentifier("idFirstSegueUnwind", sender: self) } |
現在一切都準備好了,繼續試著再次執行App。這次,你能夠在兩個視圖控制器間導覽了。
在前面部分,我們宣告一個稱做returnFromSegueActions(sender:)
的空方法,而我們說過這個方法是必須實做才能創建unwind segue。另外我也告訴你我們準備加入一些程式,現在正是要加入的時候。
所以,在這個動做方法,我們準備執行一個很簡單的任務:我們會變更第一個視圖控制器的背景顏色,然後使用一個UIView
動畫來回復為正常。即便在一個真正的應用程式中這樣作是沒有什麼意義的,但這是對於我們要示範一個unwind segue執行時,動做是如何被執行的一個很棒的範例。
一開始,至ViewController.swift
檔,然後至IBAction方阿,在其中,加入如下的內容:
1 2 3 4 5 6 7 8 9 10 |
@IBAction func returnFromSegueActions(sender: UIStoryboardSegue){ if sender.identifier == "idFirstSegueUnwind" { let originalColor = self.view.backgroundColor self.view.backgroundColor = UIColor.redColor()
UIView.animateWithDuration(1.0, animations: { () -> Void in self.view.backgroundColor = originalColor }) } } |
首先,我們先檢查這個unwind segue是否是我們所感興趣那個。然後我們儲存背景顏色,並且將顏色變更為紅色。最後,使用動畫 block,我們設定回原來的值。
以上清楚的顯示,在自訂segue被執行時,此時你要執行一個動做,會如何產生運做。稍後,我們會在以上方法多加入一些程式。
實做自訂segue(以及unwind segue)時,你必須要注意兩件事:首先,對於要產生的轉換,能讓你的App提供很棒的使用者體驗。第二,對於兩個視圖控制器間要交換的資料,不是執行標準的Segue就是unwind segue。
在前面部分,我們已經介紹過第一個狀況,我們對第一個與第二個視圖控制器間的切換指定了很棒的轉換動畫。現在,我們來看如何傳遞資料。如下你會見到的這個程序,極可能你已經使用過,儘管它以UIKit 所提供的預設Segue。
在我們開發的範例應用程式,我們並不會從一個視圖控制器傳遞至另外一個。現在我們只是互相傳送一個字串值,並顯示在我們以前在View Controller
與Second View Controller
場景加入的第二個標籤中。
一開始,打開 SecondViewController.swift
檔,在IBOutlet屬性後加入如下的屬性宣告:
1 |
var message: NSString! |
接下來,我們會使用這個屬性在Segue執行前從 ViewController
類別來傳遞一個字串值至這個控制器。
現在打開 ViewController.swift
檔。在類別主體,加入如下的函數實做內容:
1 2 3 4 5 6 |
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == "idFirstSegue" { let secondViewController = segue.destinationViewController as SecondViewController secondViewController.message = "Hello from the 1st View Controller" } } |
以上的函數是 UIViewController
類別。依照所要執行Segue的識別碼,我們存取目標視圖控制器,也就是 SecondViewController
。然後我們設定以上的字串訊息為SecondViewController
類別的message
屬性。
以上是你要使用segue傳遞任何資料至另外一個視圖控制器所須要的工做。其做法很是容易理解。
再一次,打開SecondViewController.swift
檔。是時候去處理 message
屬性,並且顯示以上傳遞後的字串。在 viewDidLoad
方法,只要加入如下這行:
1 2 3 4 5 |
override func viewDidLoad() { ...
lblMessage.text = message } |
最後,我們從第二個視圖控制器傳遞一個字串值至第一個。我們再次實做以上的方法:
1 2 3 4 5 6 |
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == "idFirstSegueUnwind" { let firstViewController = segue.destinationViewController as ViewController firstViewController.lblMessage.text = "You just came back from the 2nd VC" } } |
以上的實做,注意我們直接存取 ViewController
類別的訊息標籤。這是另外一個在視圖控制器的子視圖顯示資料的方式。注意在這兩種情況,必須要透過segue物件來存取目標視圖控制器。
這是你要在兩個視圖控制器間傳遞資料的所有內容。在第二個等會我們要自訂的segue,你會見到最後一個方式,可是這邊呈現的是大部份都會用到的。
若是能夠的話,你能夠執行App。這次,在兩個視圖控制器的第二個標籤,所傳遞的訊息是一個字串值。注意我們在SecondViewController
類別的工做已經結束。現在我們要將重點放在專案中的第三個視圖控制器。
全部我們在前面所討論過的部分,是你創建與處理自訂segue所需的觀念。不過我想多建一個,讓你能夠消化所學的內容,也能夠看見一些不一樣的狀況,我們開始吧。
第一個步驟,在介面建構器創建自訂segue,因此在專案導覽器點擊 Main.storyboard
來打開它。
介面在你的畫面上出現後,打開文件大綱面板(若是它有縮起來的話),然後在View Controller Scene
點擊View Controller
物件,並按住 Ctrl 鍵以及滑鼠按鈕,拖曳至Third View Controller Scene
的Third View Controller
物件。藍色線便會出現:
在跳出的視窗,選取 custom segue。完成後,確認你的畫面上是否有創建了新segue:
點擊以上的線,並在工具面板(Utilities pane)打開屬性檢閱器。在這裏,須要指定兩件事:segue 識別碼,以及類別。對於識別碼,在Identifier 欄位設定為idSecondSegue 然後,在Segue Class 欄位,輸入SecondCustomSegue 值,因此App能夠分辨在執行一個新的自訂segue時要使用哪個類別。
第二個自訂segue已經創建,因此我們來實做我們須要的動畫轉換。
如同我所介紹,在成為UIStoryboardSegue
類別子類別,並覆寫perform
方法,你能夠任意實做你想要的各種轉換。不論是簡單、複雜的動畫轉換或者不加均可以(可是若是你創建一個自訂Segue,卻不加入任何動畫特效是哪招?)。
針對要轉換至第三個視圖控制器的segue,我們將指定另一種動畫。這次,我們會實做一個縮小(zoom out)與放大(zoom in)的特效。明確地說,View Controller
視圖控制器的視圖會以畫框縮小來遠離,如此即可以形成縮小特效。另外一方面,第三視圖控制器的的框架,一開始會縮小,直到動畫執行後,他會變回原來的狀態。這便會產生放大特效。在這之後的內容,你會見到App 的運做,便能瞭解其意。
我們開始撰寫程式。打開SecondCustomSegue.swift
檔,如同我們已經創建過兩次一樣,我們透過區域變數的指定,將視圖控制器的視圖參照它。當然,我們的實做會在perform
方法中進行:
1 2 3 4 5 |
override func perform() { var firstVCView = sourceViewController.view as UIView! var thirdVCView = destinationViewController.view as UIView!
} |
注意你能夠跳過這個步驟並直接使用來源與目標視圖控制器的屬性來存取視圖,不過雖然須要多兩行程式,我想這樣的方式更能清楚表示。
現在,我們加入第三個視圖控制器的視圖至window中。這是你必須作的,否則便不會有第二個視圖可轉換。
1 2 3 4 5 6 7 |
override func perform() { ...
let window = UIApplication.sharedApplication().keyWindow window?.insertSubview(thirdVCView, belowSubview: firstVCView)
} |
現在,我們能夠指定第三視圖控制器其視圖的初始狀態。我們不要碰觸它的框架,我們將會修改它的transform
屬性以下所示:
1 2 3 4 5 6 |
override func perform() { ...
thirdVCView.transform = CGAffineTransformScale(thirdVCView.transform, 0.001, 0.001)
} |
如你所見,我們將寬度與高度縮減,我們不把值設為零,因為這樣沒有效果。一個很小的值便符合我們的需求。
下一步是執行動畫。在這裏,我們將會應用兩個連續的動畫,一開始,我們會先縮小原始視圖,然後我們會放大目標視圖。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
override func perform() { ...
UIView.animateWithDuration(0.5, animations: { () -> Void in firstVCView.transform = CGAffineTransformScale(thirdVCView.transform, 0.001, 0.001)
}) { (Finished) -> Void in
UIView.animateWithDuration(0.5, animations: { () -> Void in thirdVCView.transform = CGAffineTransformIdentity
}, completion: { (Finished) -> Void in
firstVCView.transform = CGAffineTransformIdentity self.sourceViewController.presentViewController(self.destinationViewController as UIViewController, animated: false, completion: nil) }) } } |
第一個動畫開始後,第一個視圖會變得很小,這會看起來像是鏡頭拉遠特效。在完成處理器(completion handler),我們開始一個新動畫,將視圖回復目標視圖控制器至正常狀態。最後,一旦動畫也結束,我們將第一個視圖控制器至它的正常狀態,且在沒有進一步動畫情況下呈現第三視圖控制器。
所有的方法的內容以下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
override func perform() { var firstVCView = sourceViewController.view as UIView! var thirdVCView = destinationViewController.view as UIView!
let window = UIApplication.sharedApplication().keyWindow window?.insertSubview(thirdVCView, belowSubview: firstVCView)
thirdVCView.transform = CGAffineTransformScale(thirdVCView.transform, 0.001, 0.001)
UIView.animateWithDuration(0.5, animations: { () -> Void in
firstVCView.transform = CGAffineTransformScale(thirdVCView.transform, 0.001, 0.001)
}) { (Finished) -> Void in
UIView.animateWithDuration(0.5, animations: { () -> Void in thirdVCView.transform = CGAffineTransformIdentity
}, completion: { (Finished) -> Void in
firstVCView.transform = CGAffineTransformIdentity self.sourceViewController.presentViewController(self.destinationViewController as UIViewController, animated: false, completion: nil) }) } } |
自訂轉換準備好了,我們還沒結束,因為我們必須執行segue。假若你記得,在View Controller
的場景,有一個按鈕,標題是「Tap me」,是我們要用來起用segue的。
首先,打開 ViewController.swift
檔,並加入如下的IBAction 方法:
1 2 3 |
@IBAction func showThirdViewController(sender: AnyObject) { self.performSegueWithIdentifier("idSecondSegue", sender: self) } |
如同所指望的,我們沒有作什麼事情,只是執行segue 而已。注意segue的識別碼必須要匹配介面建構器的設定。
最後,我們必須將按鈕連結以上的動做方法。打開Main.storyboard.swift
檔,並至文件大綱的View Controller Scene
。點擊按鈕物件,然後按住 Ctrl 鍵與滑鼠按鈕,並拖曳至View Controller
物件:
一個有許多選項的跳出選單會出現。其中有一個區塊稱做Sent Events,在下方也列出IBAction 方法名稱。選取它,連結便會成功完成。
假若你想要繼續測試App,點擊初始視圖控制器的按鈕,並看一下第三視圖控制器是怎麼呈現。
在我們範例程式中實做的第一個 unwind Segue,我說首先第一件事你必須要作的是定義一個當 segue 被執行的能夠呼叫的 IBAction 方法。在那裏我們實做一個稱做returnFromSegueActions(sender:)
的方法名稱,當它設置好後,我們也會在新的unwind segue使用它。
以上表示,我們能夠進到下一步,在介面建構器創建 unwind segue。因此,打開Main.storyboard
檔,並至Third View Controller Scene
。選取Third View Controller
物件,並按住Ctrl鍵,拖曳至 Exit 物件。
在跳出的視窗中,點擊動做方法的名稱,如此unwind segue便會創建。
接下來,選取segue,並打開屬性檢閱器,在identifier 欄位,設定idSecondSegueUnwind值。
現在unwind segue已經準備好了,我們幫想要執行的轉換行為寫程式了。這次,我們並不僅執行反向動畫至標準segue。我們準備創建稍微複雜一點的動畫,也就是第三視圖控制器會縮小,同時朝向畫面上方移動,而第一視圖控制器的的視圖將會放大,並從畫面底部往上移動。因此,它佔據了可用區域。這個視圖一開始會離開畫面的可視區域。
打開SecondCustomSegueUnwind.swift
檔,我們將會最後一次在perform
方法中實做。我們先從簡單點來:
1 2 3 4 5 6 7 |
override func perform() { var firstVCView = destinationViewController.view as UIView! var thirdVCView = sourceViewController.view as UIView!
let screenHeight = UIScreen.mainScreen().bounds.size.height
} |
同樣的,我們將兩個視圖控制器視圖存放至兩個區域變數,我們也進一步儲存畫面高度至一個變數。接下來,我們指定 firstVCView
視圖的初始狀態。如同我所說的,它將會離開畫面,然後縮小:
1 2 3 4 5 6 7 8 9 10 11 |
override func perform() { ...
firstVCView.frame = CGRectOffset(firstVCView.frame, 0.0, screenHeight) firstVCView.transform = CGAffineTransformScale(firstVCView.transform, 0.001, 0.001)
let window = UIApplication.sharedApplication().keyWindow window?.insertSubview(firstVCView, aboveSubview: thirdVCView)
} |
除了初始狀態的設定外,我們也會加入該視圖至App的視窗。
最後,我們繼續執行動畫。記得離開的視圖會縮小,而出現的視圖會放大,兩個視圖都是朝上移動。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
override func perform() { ...
UIView.animateWithDuration(0.5, animations: { () -> Void in
thirdVCView.transform = CGAffineTransformScale(thirdVCView.transform, 0.001, 0.001) thirdVCView.frame = CGRectOffset(thirdVCView.frame, 0.0, -screenHeight)
firstVCView.transform = CGAffineTransformIdentity firstVCView.frame = CGRectOffset(firstVCView.frame, 0.0, -screenHeight)
}) { (Finished) -> Void in
self.sourceViewController.dismissViewControllerAnimated(false, completion: nil) } } |
這是整個自訂unwind segue的所有,如下為其程式的全貌:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
override func perform() { var firstVCView = destinationViewController.view as UIView! var thirdVCView = sourceViewController.view as UIView!
let screenHeight = UIScreen.mainScreen().bounds.size.height
firstVCView.frame = CGRectOffset(firstVCView.frame, 0.0, screenHeight) firstVCView.transform = CGAffineTransformScale(firstVCView.transform, 0.001, 0.001)
let window = UIApplication.sharedApplication().keyWindow window?.insertSubview(firstVCView, aboveSubview: thirdVCView)
UIView.animateWithDuration(0.5, animations: { () -> Void in
thirdVCView.transform = CGAffineTransformScale(thirdVCView.transform, 0.001, 0.001) thirdVCView.frame = CGRectOffset(thirdVCView.frame, 0.0, -screenHeight)
firstVCView.transform = CGAffineTransformIdentity firstVCView.frame = CGRectOffset(firstVCView.frame, 0.0, -screenHeight)
}) { (Finished) -> Void in
self.sourceViewController.dismissViewControllerAnimated(false, completion: nil) } } |
再次回到ViewController.swift
檔,我們參訪一下segueForUnwindingToViewController(toViewController:fromViewController:identifier:)
方法。我們已經討論過它,現在我們必須檢查我們的新unwind segue,因此我初始化並回傳一個我們子類別的segue 物件。依照我們以前所作的,我們準備再創建一個檢查segue的識別碼是否匹配第二個unwind segue的識別碼的判斷,然後才能繼續。加入如下的程式來修改方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
override func segueForUnwindingToViewController(toViewController: UIViewController, fromViewController: UIViewController, identifier: String?) -> UIStoryboardSegue { if let id = identifier{ ... else if id == "idSecondSegueUnwind" { let unwindSegue = SecondCustomSegueUnwind(identifier: id, source: fromViewController, destination: toViewController, performHandler: { () -> Void in
})
return unwindSegue } }
... } |
這裏沒有什麼特別或困難的部分。現在新的 unwind segue 能夠完美執行了。不過,我們必須加上個什麼東西來啟動,我們準備在第三視圖控制器加上一個上滑手勢來處理它。因此,至ThirdViewController.swift
檔,並在 viewDidLoad
方法加入如下幾行程式:
1 2 3 4 5 6 7 |
override func viewDidLoad() { ...
var swipeGestureRecognizer: UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: "showFirstViewController") swipeGestureRecognizer.direction = UISwipeGestureRecognizerDirection.Up self.view.addGestureRecognizer(swipeGestureRecognizer) } |
剩下的部分就是實做showFirstViewController
動做方法,跟其餘類似且簡單:
1 2 3 |
func showFirstViewController() { self.performSegueWithIdentifier("idSecondSegueUnwind", sender: self) } |
現在,在我們執行App前,我們何不在第三視圖控制器解除時,在 ViewController
視圖控制器內顯示一個訊息?
這次我們不用以前所見將訊息做為資料傳遞,我們將會在returnFromSegueActions(sender:)
方法內進行。我們已經加入一些範例程式在裡面,我們說這個方法是用來執行各種動做以及一個 unwind segue。
打開 ViewController.swift
檔,於上述方內,加入如下的else
狀況:
1 2 3 4 5 6 7 |
@IBAction func returnFromSegueActions(sender: UIStoryboardSegue){ ...
else{ self.lblMessage.text = "Welcome back!" } } |
加上以上的內容後,每次當第二segue執行時會在第一視圖控制器顯示。
此時,我們的範例程式已經結束。你能夠進行測試,當然,你能夠自行變更任何部分,這樣會讓你更熟悉自訂segue。如下是App執行時動畫執行的狀況。
假若你往回快速回顧一下我們這篇文章的內容,你絕對會發現自訂segue的處理不是太困難。應用自訂動畫轉換,在不一樣視圖控制器間做導覽,能夠獲得很棒的使用者體驗,讓你的App與眾不一樣。假若自訂segue對你來說還很陌生,趕快使用它們,我強烈鼓勵你這麼作。這個我們所實做的範例,指引你正確執行自訂segue與unwind segue的方式。我們所創建的動畫與應用不是太複雜,可是對你瞭解這些全部的意義已經足夠。發揮你的想像力,並創建使人驚艷的特效。這讓你自行發揮吧,我但願你喜歡它並發現這篇文章對你有幫助,同樣的,我們期待你的見解。
為了讓你參考,你能夠在這裡下載Xcode專案。