UINavigationControllerがどうしても使えなくて諦めて普通のSegueで対応しているところです。
navigationControllerなら左右にスライドアニメーションで遷移する(らしい)がどうにもこうにも上手く使えないのでカスタムSegueで左右にスライドするアニメーションを作成しました。
※UnwindのアニメーションはiOS9でのみの対応となります。iOS7,8では動きませんでした。
※iOS9上ではこちらの方法で動作しますが、iOS8以下では動作しません。
iOS8以下での必要なコードをこちらの記事に書きました
→【Swift】カスタムUnwindSegueを作成する【iOS8編】
カスタムSegueを作成する
まずは新規ファイルでカスタムSegue用のファイルを作ります
File > New > File....からCocoaTouchClassを選択します。
そしてSubclassをUIStoryboardSegueに設定してファイルを作成します
作成ファイルを編集していきます。
override func perform(){
//ここにコードを書いていきます
}
perofmメソッドをoverrideしてその中にコードを書いていきます。
まずは遷移前後のViewControllerのインスタンスをそれぞれ作成。
//遷移前のViewControllerのインスタンスを作成
let firstVCView = self.sourceViewController.view as UIView!
//遷移後のViewControllerのインスタンスを作成
let secondVCView = self.destinationViewController.view as UIView!
次に画面の縦横をそれぞれ取得しときます
//画面の横の長さを取得
let screenWidth = UIScreen.mainScreen().bounds.size.width
//画面の縦の長さを取得
let screenHeight = UIScreen.mainScreen().bounds.size.height
そしてアニメーション前の遷移後のビューの位置を設定しておきます。
//遷移後のビューを画面の外(右側)にだしておく
secondVCView.frame = CGRectMake(screenWidth, 0.0, screenWidth, screenHeight)
遷移先をサブビューに設定しておく(?)
let window = UIApplication.sharedApplication().keyWindow
window?.insertSubview(secondVCView, aboveSubview: firstVCView)
UIWindowについてよくわかっていないのですが、こういうことらしい・・
At this point, the view of the second view controller is not a subview of the app’s window yet. So, before we implement the actual animation, it’s obvious that we must add it to the window. This will be achieved using the insertSubview(view:aboveSubview:) method of the app’s window object. As you see next, at first we access the window object, and then we add the destination view:
英語が読めないわけじゃなくてですね・・・・
ほわぁ〜っとしたのはわかるんですけど説明できないというか、そのまま訳せばいいんですけども。。。
先にwindow objectにアクセスして、遷移前の画面の上に遷移後のビューをのせる、ということ?
次にアニメーションをつけます
//0.4秒で遷移する
UIView.animateWithDuration(0.4, animations: { () -> Void in
//遷移前のビューを現在の位置から画面幅分移動する
firstVCView.frame = CGRectOffset(firstVCView.frame, -screenWidth, 0.0)
//遷移後のビューを現在の位置(画面外)から画面幅分移動する
secondVCView.frame = CGRectOffset(secondVCView.frame, -screenWidth, 0.0)
}) { (Finished) -> Void in
self.sourceViewController.presentViewController(self.destinationViewController as UIViewController,
animated: false,
completion: nil)
}
このアニメーションの中をいじることで色々なアニメーションをつけることができます。
まずは画面に表示されている遷移前のビューを画面外に押しやります。
それと同時に遷移後のビューを画面外から画面上に移動させます。(先ほど設定した位置からの移動になります)
アニメーションが終了したら画面を表示させます。animetedをtrueにしてしまうとスライド後に再度下から表示するアニメーションをしてしまいます。
つまりwindowは見かけ上の画面を取得するということなのかな。むむむ。わからん。
Storyboard上でカスタムセグエにしておくことを忘れずに
Classが先ほど作成したファイル名になっていればOKです。
カスタムUnwindSegueを作成する
次にカスタムUnwindSegueを作成していきます。
なかなか上手くいかなくて大変でしたが、成功したら意外と単純でした。
UnwindSegueを作成する際には、まず遷移先(前のページ)にこのコードを書いておきます
@IBAction func unwindToSubject(segue:UIStoryboardSegue){
//戻った後の処理を書く
}
これがなければUnwindを設定することができます。
FirstViewController からSecondViewControllerに遷移していて、そこからUnwindでFirstViewControllerに戻る場合には、
そしてStoryboard上でSegueをつなげます。
このときにUnwindSegueのClassに作成しておいたUnwindSegueのファイルを設定します。(作り方はSegueと同様)
以前まではこの設定ができなかったそうで、設定ができない場合の記事は色々出てきたんですが、ない場合の記事が出てこなくてひっかかってしまいました。
結局やったことはSegueの時とほとんど同じです。
先ほどと同様にUnwindSegueのファイルを作成します。
import UIKit
class myUnwindSegue: UIStoryboardSegue {
override func perform() {
//ViewControllerのインスタンスを取得する
let firstVCView = destinationViewController.view as UIView!
let secondVCView = sourceViewController.view as UIView!
//画面の縦横を取得する
let screenHeight = UIScreen.mainScreen().bounds.size.height
let screenWidth = UIScreen.mainScreen().bounds.size.width
//戻った先のビューを画面外に設置する。
firstVCView.frame = CGRectMake(-screenWidth, 0.0, screenWidth, screenHeight)
//戻った先のビューを現在の画面の上にのせる
let window = UIApplication.sharedApplication().keyWindow
window?.insertSubview(firstVCView, aboveSubview: thirdVCView)
UIView.animateWithDuration(0.5, animations: { () -> Void in
//現在のビューを画面外に移動させる。
secondVCView.frame = CGRectOffset(secondVCView.frame, screenWidth, 0.0)
//戻った先のビューを画面上に移動させる。
firstVCView.frame = CGRectOffset(firstVCView.frame, screenWidth, 0.0)
}) { (Finished) -> Void in
//現在の画面を閉じる
self.sourceViewController.dismissViewControllerAnimated(false, completion: nil)
}
}
}
こんな感じ。
Segueと異なるところは画面の閉じ方ぐらいでしょうか。
あとはFistViewControllerとSecondViewControllerの役割が反対になるので注意が必要です。
補足しました
iOS8以下ではさらにこちらのコードも必要です
override func segueForUnwindingToViewController(toViewController: UIViewController, fromViewController: UIViewController, identifier: String?) -> UIStoryboardSegue? {
if let id = identifier {
print("id=identifier")
let unwindSegue = myUnwindSegue(identifier: id, source: fromViewController, destination: toViewController, performHandler: { () -> Void in
})
return unwindSegue
}
return super.segueForUnwindingToViewController(toViewController, fromViewController: fromViewController, identifier: identifier)!
}
ざっと別記事も書きましたが必要なのはこのコードだけです。【Swift】カスタムUnwindSegueを作成する【iOS8編】
iOS9でもStoryboard上Classを設定しない場合はこちらのコードが必要です。
できたコードを見ると、なんだ(´・ω・`)って感じなんですがなかなかにハマりました(笑)
Xcode7.1環境ですが、Unwindセグエもカスタムセグエがしやすくなっていて嬉しいですね。
遷移後にエラーが発生した・・・・
SegueのコードをコピーしてそのままUnwindに貼り付けて、その後FirstVCViewとSecondVCViewを入れ替えただけのコードにしたところこんなエラーが出ました。
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Application tried to present modally an active
こいつをやっつけるのに時間がかかったわけなんですが
このエラーは「同時タップによる多重遷移」のせいらしいです。つまり
最初のカスタムSegueでは、アニメーション後に遷移先の画面を新しく表示させています。
カスタムUnwindSegueでは、アニメーション後に遷移前の画面を閉じることで、遷移後の画面を表示しています。
UnwindSegueでは遷移後の画面を開くコードがまた別のところにあるというわけだろうか。それとも上に順繰り重なっていっているからなのだろうか。要はモーダルで重なっているのと同じ?なのか。
詳しいことはあまり理解できていませんが、上のように対処することでエラーを回避できました。
とにかくアニメーションが思った通りに動いてくれるとすごく嬉しいし楽しいです。
これでもうセグエなんて怖くないぜ!!!!!(なおnavigationControllerはめちゃくちゃ手強い模様)
間違っているところを発見した方やUIWindowについての説明がわかりやすくできる方がいたらぜひ教えてください。
参考リンク
- [【Swift】Custom Segueを作る方法](http://gokexn.blog.fc2.com/blog-entry-111.htmlApplication tried to present modally an active controller)
- A Beginner’s Guide to Animated Custom Segues in iOS 8
- はじめてのiPhoneアプリ - カメラアプリを作る際にハマったことのメモ
Additional Notes追記
Swift4に対応しました。
対応したらコードが全然違うものになりました。 っていうかやり方も全然違う感じになってしまった。
一応上記そのままSwift4に移行したコードを書いてみたんですが、 Unbalanced calls to begin/end appearance transitionsってエラーでちゃんとアニメーションしませんでした。
なので「ios - Unbalanced calls to begin/end appearance transitions with custom segue - Stack Overflow」を参考に上記のように変更しました。
アニメーション部分を自由にいじって実際に試してみてください〜。 CustomSegue自体は本記事通りでおkです。
unwindの方はとりあえず以下の部分だけ変えれば動きます。
UIView.animate(withDuration: 0.5, animations: {
mock.frame.origin.x = 0
}) { (isCompletion) in
if isCompletion {
//unwindの時はここだけ変える
self.source.dismiss(animated: false, completion: { mock.removeFromSuperview() })
}else {
mock.removeFromSuperview()
}
}
単純に先ほどのコードを1行だけdismissに変更しました。
こんな感じになります。
これだと両方ともが上に重なる形で遷移してしまうので Unwindの方は現在表示されているviewが元の場所に戻る形になるように実装してみました。
要は遷移前に今のviewをスクリーンショットとして残して置いて、そいつを後から動かすってことをしてます。
CustomSegue、CustomUnwindSegueの両方ともSwiftファイルのみこちらで、後のIB上での実装方法等は本記事通りです。
ではでは。 わりとスッとできて成長を感じるぜ・・・!
コメントはありません。
現在コメントフォームは工事中です。