- 投稿日:2019-02-15T21:56:14+09:00
iOS 12.2 beta で PWA 対応が進展してたらしい
iOSの対応状況ってどんなだろうと調べたら割とタイムリーだったので。
雑にリンクをシェアする
https://medium.com/@firt/pwas-on-ios-12-2-beta-the-good-the-bad-and-the-not-sure-yet-if-good-a37b6fa6afbf
https://medium.com/dev-channel/progressive-web-app-progress-in-ios-12-2-beta-1-build-16e5181f-a18cd05ca361どんな
詳しくは上のリンクを。以下特に確かめてません。
- Web Share API level 1
- 知らなかったけどこういうセマンティックなAPI好き
- マルチタスク時にも状態を維持
- Page Lifecycle API未実装
- 例えばバックグラウンドになったイベント、戻ってきたイベントで処理を発火させることなどができない
- Abortable Fetch
- fetchをタイムアウトさせられる。
- ジェスチャーによるナビゲーション(右にスワイプで戻るとか)
- カラーピッカー的なフォーム
- Experimental で気になったやつ
- Media Recorder
- Web Authentication
など。
こういう記事を書くととりあえずそれが何者であるか程度は調べるので健康に良いですね。雑リンク(参考):11.3のとき
- 投稿日:2019-02-15T20:50:43+09:00
iOSでAndroidのFragmentのような実装をする (iOSで画面入れ替え)
はじめに
iOSでAndroidのFragmentのような実装をしたいと考えました。
AndroidのFragmentのような…というのは、ここでは1画面の中に固定View(Activity)と複数可変View(Fragment)があり、可変Viewがそれぞれ入れ替わるようなイメージしています。
実現方法
今回は、ボタンを押したら2つのViewが切り替わるサンプルをつくりました。
storyboard
このような構造にしました。薄い青いViewがContainerです。ViewController
Swiftは4.2です。
ViewController.swiftclass ViewController: UIViewController { @IBOutlet weak var container:UIView! let redView:UIView = UINib(nibName: "RedView", bundle:nil).instantiate(withOwner: self, options: nil)[0] as! UIView let blueView:UIView = UINib(nibName: "BlueView", bundle:nil).instantiate(withOwner: self, options: nil)[0] as! UIView var changeFlag = false override func viewDidLoad() { super.viewDidLoad() changeView() } func changeView() { let width = self.container.bounds.size.width//containerの横幅を取得 let height = self.container.bounds.size.height//containerの縦幅を取得 let frame = CGRect(x:0, y:0, width:width, height:height)//containerの幅に設定 //フラグによってViewをaddSubView、removeFromSuperviewして入れ替える if changeFlag { blueView.removeFromSuperview() redView.frame = frame container.addSubview(redView) changeFlag = false } else { redView.removeFromSuperview() blueView.frame = frame container.addSubview(blueView) changeFlag = true } } @IBAction func click(sender:AnyObject){ changeView() } }addSubViewと、removeFromSuperviewを用いてContainerの中にViewを追加したり削除したりし、Viewの入れ替えを実現しました 。
DEMO
このように、ボタンを押したら青と赤のViewが入れ替わります!
全ソース
サンプルコードはこちらに置いてあります!
https://github.com/mii-chang/ChangeViewSample参考になれば嬉しいです◎!
- 投稿日:2019-02-15T19:08:13+09:00
最新バージョンのdSYMアップロードをほぼ自動化する
fastlaneのdownload_dsymsとupload_symbos_to_crashlyticsを組み合わせれば,dSYMのダウンロードからアップロードまでを簡単に自動化できます.
ただし,bitcodeを有効にしている場合には,App Store Connect上での処理完了を待つ必要があります.メール受信をトリガにする方法などもありますが,自分のチームではもう少しお手軽な方法で(ほぼ)自動化しているので,そのやり方を紹介します.
まずは以下のlaneを作成します.
Fastfilelane :refresh_dsyms do |options| version = options[:version] || 'latest' download_dsyms( version: version ) upload_symbols_to_crashlytics( api_token: ENV['CRASHLYTICS_API_TOKEN'] ) clean_build_artifacts end作成したlaneは以下のように使用します.
# 最新バージョンのdSYMをアップロード $ fastlane refresh_dsyms # 任意のバージョンのdSYMをアップロード $ fastlane refresh_dsyms version:1.2.3自分のチームでは,これらを以下のようなやり方で運用しています.
- 毎週,CI上で
fastlane refresh_dsyms
を実行し、最新バージョンのdSYMに更新
- 毎週1回のペース(金曜に申請し、翌週リリース)で定期的にリリースをしているので、
土曜0時に実行すれば最新版のdSYMに更新できる- 非定期リリース(Hotfixなど)があれば,ローカルからバージョンを指定(=
version: x.x.x
を指定)してdSYMを更新ローカルでの実行も混ざり得るのでもっといい感じの運用にしたいところではありますが,今のところ定期的なリリースサイクルを回せているので,手動での更新はほとんど必要ないです.
チームのスケジュールを考慮することで,完全自動化までは行かなくとも,それに近い結果を手軽得ることができます.
- 投稿日:2019-02-15T17:06:06+09:00
[iOS]Swift4で音声の入力・出力をする方法
はじめに
この記事では、マイクから音声を入力して一旦ファイル化し、その音をスピーカーから出力するまでの処理をSwift4で書いてみることにします。
Swiftのアップデートが早くてSwift4もすぐにオワコンになってしまうかもしれないですが、自分用の備忘も兼ねて記録しておきます使ったもの
- Xcode 10.1
- Swift 4.2
- AVFoundation
共通で必要な処理の実装
AVFoundationのインスタンスを生成する
let session = AVAudioSession.sharedInstance()再生と録音をすることを宣言してからアクティブ化する
try session.setCategory(.playAndRecord, mode: .default) try session.setActive(true)音声入力の実装
まず録音で使用するAVAudioRecorderを宣言する
var audioRecorder: AVAudioRecorder!録音フォーマットの設定をする
let settings = [ AVFormatIDKey: Int(kAudioFormatMPEG4AAC), AVSampleRateKey: 44100, AVNumberOfChannelsKey: 2, AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue ]AVAudioRecorderインスタンスを生成する
audioRecorder = try AVAudioRecorder(url: getAudioFileUrl(), settings: settings) audioRecorder.delegate = self as? AVAudioRecorderDelegatefunc getAudioFileUrl() -> URL { let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) let docsDirect = paths[0] let audioUrl = docsDirect.appendingPathComponent("recording.m4a") return audioUrl }ボタンを押したら録音開始(録音中に押したら録音停止)
@IBAction func record(_ sender: Any) { if !audioRecorder.isRecording { audioRecorder.record() } else { audioRecorder.stop() } }音声出力の実装
まず再生で使用するAVAudioEngine、AVAudioFile、AVAudioPlayerNodeを宣言する
var audioEngine: AVAudioEngine! var audioFile: AVAudioFile! var audioPlayerNode: AVAudioPlayerNode!AVAudioEngine、AVAudioFile、AVAudioPlayerNode各々のインスタンスを生成する
audioEngine = AVAudioEngine() audioFile = try AVAudioFile(forReading: getAudioFileUrl()) audioPlayerNode = AVAudioPlayerNode()//上で書いたfuncと同じ func getAudioFileUrl() -> URL { let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) let docsDirect = paths[0] let audioUrl = docsDirect.appendingPathComponent("recording.m4a") return audioUrl }Nodeのつなぎ合わせをする
audioEngine.attach(audioPlayerNode) audioEngine.connect(audioPlayerNode, to: audioEngine.outputNode, format: audioFile.processingFormat)ボタンを押したら再生開始(再生中に押したら初めから再生)
@IBAction func play(_ sender: Any) { do { //中略(インスタンス生成やNodeのつなぎ合わせはここでやる) audioPlayerNode.stop() audioPlayerNode.scheduleFile(audioFile, at: nil) try audioEngine.start() audioPlayerNode.play() } catch let error { print(error) } }通しで書くとこんな感じ
import UIKit import AVFoundation class ViewController: UIViewController { var audioRecorder: AVAudioRecorder! var audioEngine: AVAudioEngine! var audioFile: AVAudioFile! var audioPlayerNode: AVAudioPlayerNode! override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. let session = AVAudioSession.sharedInstance() do { try session.setCategory(.playAndRecord, mode: .default) try session.setActive(true) let settings = [ AVFormatIDKey: Int(kAudioFormatMPEG4AAC), AVSampleRateKey: 44100, AVNumberOfChannelsKey: 2, AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue ] audioRecorder = try AVAudioRecorder(url: getAudioFileUrl(), settings: settings) audioRecorder.delegate = self as? AVAudioRecorderDelegate } catch let error { print(error) } } @IBAction func record(_ sender: Any) { if !audioRecorder.isRecording { audioRecorder.record() } else { audioRecorder.stop() } } @IBAction func play(_ sender: Any) { audioEngine = AVAudioEngine() do { audioFile = try AVAudioFile(forReading: getAudioFileUrl()) audioPlayerNode = AVAudioPlayerNode() audioEngine.attach(audioPlayerNode) audioEngine.connect(audioPlayerNode, to: audioEngine.outputNode, format: audioFile.processingFormat) audioPlayerNode.stop() audioPlayerNode.scheduleFile(audioFile, at: nil) try audioEngine.start() audioPlayerNode.play() } catch let error { print(error) } } func getAudioFileUrl() -> URL { let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) let docsDirect = paths[0] let audioUrl = docsDirect.appendingPathComponent("recording.m4a") return audioUrl } }こんなクラスファイルがあると実装が楽
この辺のユーティリティと言うほどではないけれど、いろんな画面から呼び出しそうな処理はこんな感じで一箇所にまとめて外出ししてしまった方がコードが冗長にならなくていいなあと思います
あくまで一例ですが…AudioManager.swiftimport UIKit import AVFoundation final class AudioManager { var audioRecorder: AVAudioRecorder! var audioEngine: AVAudioEngine! var audioFile: AVAudioFile! var audioPlayerNode: AVAudioPlayerNode! var audioUnitTimePitch: AVAudioUnitTimePitch! init() {} func setUpAudioRecorder() { let session = AVAudioSession.sharedInstance() do { try session.setCategory(.playAndRecord, mode: .default) try session.setActive(true) let settings = [ AVFormatIDKey: Int(kAudioFormatMPEG4AAC), AVSampleRateKey: 44100, AVNumberOfChannelsKey: 2, AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue ] audioRecorder = try AVAudioRecorder(url: getAudioFilrUrl(), settings: settings) audioRecorder.delegate = self as? AVAudioRecorderDelegate } catch let error { print(error) } } func playSound(speed: Float, pitch: Float, echo: Bool, reverb: Bool) { audioEngine = AVAudioEngine() let url = getAudioFilrUrl() do { audioFile = try AVAudioFile(forReading: url) audioPlayerNode = AVAudioPlayerNode() audioEngine.attach(audioPlayerNode) audioUnitTimePitch = AVAudioUnitTimePitch() audioUnitTimePitch.rate = speed audioUnitTimePitch.pitch = pitch audioEngine.attach(audioUnitTimePitch) let echoNode = AVAudioUnitDistortion() echoNode.loadFactoryPreset(.multiEcho1) audioEngine.attach(echoNode) let reverbNode = AVAudioUnitReverb() reverbNode.loadFactoryPreset(.cathedral) reverbNode.wetDryMix = 50 audioEngine.attach(reverbNode) if echo && reverb { connectAudioNodes(audioPlayerNode, audioUnitTimePitch, echoNode, reverbNode, audioEngine.outputNode) } else if echo { connectAudioNodes(audioPlayerNode, audioUnitTimePitch, echoNode, audioEngine.outputNode) } else if reverb { connectAudioNodes(audioPlayerNode, audioUnitTimePitch, reverbNode, audioEngine.outputNode) } else { connectAudioNodes(audioPlayerNode, audioUnitTimePitch, audioEngine.outputNode) } audioPlayerNode.stop() audioPlayerNode.scheduleFile(audioFile, at: nil) try audioEngine.start() audioPlayerNode.play() } catch let error { print(error) } } private func connectAudioNodes(_ nodes: AVAudioNode...) { for x in 0..<nodes.count - 1 { audioEngine.connect(nodes[x], to: nodes[x+1], format: audioFile.processingFormat) } } func getAudioFilrUrl() -> URL { let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) let docsDirect = paths[0] let audioUrl = docsDirect.appendingPathComponent("recording.m4a") return audioUrl } }おわりに
単に音声の入出力だけでも結構色々やらなきゃいけないことが多いみたいでなれるまでは大変かもしれませんね。
個人的にはNodeをつなぎ合わせるという考え方が、楽器→エフェクター→アンプの接続を想起させるような作りになっていて面白いと思いました
- 投稿日:2019-02-15T12:21:35+09:00
2019年2月のApple Developerのポリシー変更(Apple IDの2ファクタ認証が必須になる件ほか)
2月14日にApple Developerサポートからこんなメールが飛んできました。
見逃している方もいらっしゃるかと思ってシェアいたします。
Apple Developerで使っている Apple IDの2ファクタ認証が必須になるとのことです。Dear XXXX,
In an effort to keep your account more secure, two-factor authentication will be required to sign in to your Apple Developer account and Certificates, Identifiers & Profiles starting February 27, 2019.
This extra layer of security for your Apple ID helps ensure that you're the only person who can access your account.
If you haven't already enabled two-factor authentication for your Apple ID, please learn more and update your security settings.If you have any questions, contact us.
Best regards,
Apple Developer Relationsということで、早めに設定しておかないと、慌てることになりそうです。
また、Apple DeveloperとApp Store Connectのメンバーおよびロールが統合されるとのこと。
https://developer.apple.com/support/teams/こちらは2月12日から開始されているそうです。
- 投稿日:2019-02-15T11:36:25+09:00
【Swift】画面の一部でタップの反応が遅い時の対処
概要
アプリを作っていて画面の一部をホールドすることでアクションを起こそうとしていたがその処理が画面端の時だけ遅れていて対処にハマったのでその対処法をメモ。
実装環境
Xcode 10.1
Swift 4.2.1コード
override func viewDidAppear(_ animated: Bool) { let window = view.window! let gr0 = window.gestureRecognizers![0] as UIGestureRecognizer let gr1 = window.gestureRecognizers![1] as UIGestureRecognizer gr0.delaysTouchesBegan = false gr1.delaysTouchesBegan = false }解説
どうやらgestureRecognizersの最初の二つがデフォルトで遅延するようになっているので上記のコードをViewControllerクラス内に書き込めばtouchesBeganの遅延が解消されるようだ。
参考
UISystemGateGestureRecognizer and delayed taps near bottom of screen
- 投稿日:2019-02-15T08:14:14+09:00
【iOS】アプリ画面を縦固定に制御する
- 投稿日:2019-02-15T00:13:40+09:00
iOSのページングの仕組みを紐解いたら有能でした
iOSのUIPageViewControllerは思ったより有能だったという話です。
ただ「どう有能なのか」を理解するのに自分は思ったより時間がかかったので、まとめです。※UIPageViewControllerはこんな感じのもの。
前提
- UIScrollViewは今回使ってません。
- ボタン等でPageViewを遷移させる場合は今回の話対象外です。UIPageViewControllerDataSourceのメソッドが呼ばれないので。
開発環境
- Xcode 10.1
- Swift 4.2
通常のUIPageViewControllerの実装方法
4枚のViewControllerをUIPageViewControllerで切り替えできるように実装する場合、
下記のような感じのコードになると思います。サンプルです。
Sample.storyboard
上にSamplePageViewController
と4つのViewControllerがある- 他の画面から
SamplePageViewController
に遷移することを想定してるclass SamplePageViewController: UIPageViewController { override func viewDidLoad() { super.viewDidLoad() dataSource = self setViewControllers([getFirst()], direction: .forward, animated: true, completion: nil) } func getFirst() -> UIViewController { let storyBoard = UIStoryboard(name: "Sample", bundle: nil) let viewController = storyBoard.instantiateViewController(withIdentifier: "FirstViewController") return viewController } func getSecond() -> UIViewController { let storyBoard = UIStoryboard(name: "Sample", bundle: nil) let viewController = storyBoard.instantiateViewController(withIdentifier: "SecondViewController") return viewController } func getThird() -> UIViewController { let storyBoard = UIStoryboard(name: "Sample", bundle: nil) let viewController = storyBoard.instantiateViewController(withIdentifier: "ThirdViewController") return viewController } func getFourth() -> UIViewController { let storyBoard = UIStoryboard(name: "Sample", bundle: nil) let viewController = storyBoard.instantiateViewController(withIdentifier: "FourthViewController") return viewController } } extension SamplePageViewController: UIPageViewControllerDataSource { func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { if viewController.isKind(of: FourthViewController.self) { // 今Fourthなら、Before画面はThird return getThird() } else if viewController.isKind(of: ThirdViewController.self) { // 今Thirdなら、Before画面はSecond return getSecond() } else if viewController.isKind(of: SecondViewController.self) { // 今Secondなら、Before画面はFirst return getFirst() } else { return nil } } func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { if viewController.isKind(of: FirstViewController.self) { // 今Firstなら、After画面はSecond return getSecond() } else if viewController.isKind(of: SecondViewController.self) { // 今Secondなら、After画面はThird return getThird() } else if viewController.isKind(of: ThirdViewController.self) { // 今Thirdなら、After画面はFourth return getFourth() } else { return nil } } }storyboard使ってるとかView周りの扱い方は人それぞれだと思います、
今回はView周りの設定には言及しません。
UIPageViewControllerDataSource
で2つのメソッドを利用しています。
以降、呼びやすいようにAfterPage
・BeforePage
と表記します。AfterPageとBeforePageを使う場合について!この記事では言及します!
AfterPageとBeforePageの問題
AfterPageとBeforePageとは、
上記のコードを見れ貰えれば分かる人は分かると思いますが、
ページを左右にフリックで遷移できるように設定している部分です。
- AfterPage: →側に遷移する際の画面の設定
- BeforePage: ←側に遷移する際の画面の設定
このイベントに合わせて、各々のページに追加アクションさせようとすると、事件が発生します。
例えば、func getFirst() -> UIViewController { let storyBoard = UIStoryboard(name: "Sample", bundle: nil) let viewController = storyBoard.instantiateViewController(withIdentifier: "FirstViewController") navigationItem.title = "Firstやで" // <-追加 return viewController }このように、遷移に合わせてtitleを変更するように書いてみます。(4画面とも
-> なぜか思うようにtitleが変わってくれない。。。なんで。。。調べていくと、AfterPageとBeforePageが呼ばれるタイミングに問題がありました!
「フリックする度に1回呼ばれる」は間違っていました!フロー図でAfterPage・BeforePageのロードを追う
First->Second->Third->Fourth->Third->Second->Firstで遷移する例を基に、
フロー図でAfterPage・BeforePageのロードを追ってみます。
- 補足1: ロード = AfterPage or BeforePageの呼び出し。
- 補足2: ロードすると、ロードしたViewをPageViewControllerが持つことになります。
フロー図から見えた要点
- PageViewControllerは最大、表示Viewと前後の合計3つまでViewを持つ
- 最初は、最初の画面(First)しか持っていない
- フリックをしようとして、遷移先のページを持っていなければ、AfterPage or BeforePageをロードしてページを取得する
- フリックでページングの遷移が完了すると、表示Viewの前後のViewを確認し、なければAfterPage or BeforePageをロードして取得する
- 持っているViewが、表示Viewまたは前後のViewでなければ破棄する
※要点多くなって要点っぽくなくなってしまった(:3」∠)
PageViewControllerが持つViewを3つに自動で設定してくれていて有能!!
と僕は感じました。おかげでViewを保持容量を制御してくれているので。結論1:遷移のタイミングでアクションを追加したいなら
AfterPage or BeforePageのタイミングに追加するのはオススメしません。
その代わり、UIPageViewControllerDelegateに別の便利メソッドがあります。extension PageViewController: UIPageViewControllerDelegate { // ページ遷移が完了したら、titleを変更する func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { // viewControllers?[0] は現在のViewControllerを示す guard completed, let currentViewController = viewControllers?[0] else { return } switch currentViewController { case is FirstViewController: navigationItem.title = "First" case is SecondViewController: navigationItem.title = "Second" case is ThirdViewController: navigationItem.title = "Third" case is FourthViewController: navigationItem.title = "Fourth" default: break } } }ページ遷移が完了したタイミングで呼び出されるメソッドを利用すれば、
titleを変更する処理を問題なく実現できました。結論2:AfterPageとBeforePageの内容を最適化するなら
BeforePageの下記コードは不要です。
if viewController.isKind(of: FourthViewController.self) { // 今Fourthなら、Before画面はThird return getThird() }なぜなら、Fourthの時点で、PageViewControllerがThirdを持っていないことはないからです。
そのため、上記コードは省略できます。まとめ
省略しないで書くことを勧めます。w
説明をちゃんとしないと省略していい理由が分からないですし。
可読性を考えると、省略しないことを勧めます。PageViewControllerの有能な点を理解して、活かした実装にしましょう。
補足:たまにPageView完了時にAfterPage呼び出されない
First -> Secondに遷移した際に、Thirdを読み込まないケースが自分の場合ありました。
フリックの勢いのせいかなwくらいしか検討がつかなくて、
原因はあんまり良くわかっていないので、知っている人いたら教えてくださいm(__)m参考サイト
https://swiswiswift.com/2018/06/21/page-view-controller-with-page-control/
https://qiita.com/Takeshi_Akutsu/items/dbf54df8e8a50e8ed5be
https://qiita.com/yajamon/items/e1754e7fc847b595c26a
- 投稿日:2019-02-15T00:13:40+09:00
iOSのページングの仕組みを紐解いてフロー図にしてみた
iOSのUIPageViewControllerは思ったより有能だったという話です。
ただ「どう有能なのか」を理解するのに自分は思ったより時間がかかったので、まとめです。※UIPageViewControllerはこんな感じのもの。
前提
- UIScrollViewは今回使ってません。
- ボタン等でPageViewを遷移させる場合は今回の話対象外です。UIPageViewControllerDataSourceのメソッドが呼ばれないので。
開発環境
- Xcode 10.1
- Swift 4.2
通常のUIPageViewControllerの実装方法
4枚のViewControllerをUIPageViewControllerで切り替えできるように実装する場合、
下記のような感じのコードになると思います。サンプルです。
Sample.storyboard
上にSamplePageViewController
と4つのViewControllerがある- 他の画面から
SamplePageViewController
に遷移することを想定してるclass SamplePageViewController: UIPageViewController { override func viewDidLoad() { super.viewDidLoad() dataSource = self setViewControllers([getFirst()], direction: .forward, animated: true, completion: nil) } func getFirst() -> UIViewController { let storyBoard = UIStoryboard(name: "Sample", bundle: nil) let viewController = storyBoard.instantiateViewController(withIdentifier: "FirstViewController") return viewController } func getSecond() -> UIViewController { let storyBoard = UIStoryboard(name: "Sample", bundle: nil) let viewController = storyBoard.instantiateViewController(withIdentifier: "SecondViewController") return viewController } func getThird() -> UIViewController { let storyBoard = UIStoryboard(name: "Sample", bundle: nil) let viewController = storyBoard.instantiateViewController(withIdentifier: "ThirdViewController") return viewController } func getFourth() -> UIViewController { let storyBoard = UIStoryboard(name: "Sample", bundle: nil) let viewController = storyBoard.instantiateViewController(withIdentifier: "FourthViewController") return viewController } } extension SamplePageViewController: UIPageViewControllerDataSource { func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { if viewController.isKind(of: FourthViewController.self) { // 今Fourthなら、Before画面はThird return getThird() } else if viewController.isKind(of: ThirdViewController.self) { // 今Thirdなら、Before画面はSecond return getSecond() } else if viewController.isKind(of: SecondViewController.self) { // 今Secondなら、Before画面はFirst return getFirst() } else { return nil } } func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { if viewController.isKind(of: FirstViewController.self) { // 今Firstなら、After画面はSecond return getSecond() } else if viewController.isKind(of: SecondViewController.self) { // 今Secondなら、After画面はThird return getThird() } else if viewController.isKind(of: ThirdViewController.self) { // 今Thirdなら、After画面はFourth return getFourth() } else { return nil } } }storyboard使ってるとかView周りの扱い方は人それぞれだと思います、
今回はView周りの設定には言及しません。
UIPageViewControllerDataSource
で2つのメソッドを利用しています。
以降、呼びやすいようにAfterPage
・BeforePage
と表記します。AfterPageとBeforePageを使う場合について!この記事では言及します!
AfterPageとBeforePageの問題
AfterPageとBeforePageとは、
上記のコードを見れ貰えれば分かる人は分かると思いますが、
ページを左右にフリックで遷移できるように設定している部分です。
- AfterPage: →側に遷移する際の画面の設定
- BeforePage: ←側に遷移する際の画面の設定
このイベントに合わせて、各々のページに追加アクションさせようとすると、事件が発生します。
例えば、func getFirst() -> UIViewController { let storyBoard = UIStoryboard(name: "Sample", bundle: nil) let viewController = storyBoard.instantiateViewController(withIdentifier: "FirstViewController") navigationItem.title = "Firstやで" // <-追加 return viewController }このように、遷移に合わせてtitleを変更するように書いてみます。(4画面とも
-> なぜか思うようにtitleが変わってくれない。。。なんで。。。調べていくと、AfterPageとBeforePageが呼ばれるタイミングに問題がありました!
「フリックする度に1回呼ばれる」は間違っていました!フロー図でAfterPage・BeforePageのロードを追う
First->Second->Third->Fourth->Third->Second->Firstで遷移する例を基に、
フロー図でAfterPage・BeforePageのロードを追ってみます。
- 補足1: ロード = AfterPage or BeforePageの呼び出し。
- 補足2: ロードすると、ロードしたViewをPageViewControllerが持つことになります。
フロー図から見えた要点
- PageViewControllerは最大、表示Viewと前後の合計3つまでViewを持つ
- 最初は、最初の画面(First)しか持っていない
- フリックをしようとして、遷移先のページを持っていなければ、AfterPage or BeforePageをロードしてページを取得する
- フリックでページングの遷移が完了すると、表示Viewの前後のViewを確認し、なければAfterPage or BeforePageをロードして取得する
- 持っているViewが、表示Viewまたは前後のViewでなければ破棄する
※要点多くなって要点っぽくなくなってしまった(:3」∠)
PageViewControllerが持つViewを3つに自動で設定してくれていて有能!!
と僕は感じました。おかげでViewを保持容量を制御してくれているので。結論1:遷移のタイミングでアクションを追加したいなら
AfterPage or BeforePageのタイミングに追加するのはオススメしません。
その代わり、UIPageViewControllerDelegateに別の便利メソッドがあります。extension PageViewController: UIPageViewControllerDelegate { // ページ遷移が完了したら、titleを変更する func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { // viewControllers?[0] は現在のViewControllerを示す guard completed, let currentViewController = viewControllers?[0] else { return } switch currentViewController { case is FirstViewController: navigationItem.title = "First" case is SecondViewController: navigationItem.title = "Second" case is ThirdViewController: navigationItem.title = "Third" case is FourthViewController: navigationItem.title = "Fourth" default: break } } }ページ遷移が完了したタイミングで呼び出されるメソッドを利用すれば、
titleを変更する処理を問題なく実現できました。結論2:AfterPageとBeforePageの内容を最適化するなら
BeforePageの下記コードは不要です。
if viewController.isKind(of: FourthViewController.self) { // 今Fourthなら、Before画面はThird return getThird() }なぜなら、Fourthの時点で、PageViewControllerがThirdを持っていないことはないからです。
そのため、上記コードは省略できます。まとめ
省略しないで書くことを勧めます。w
説明をちゃんとしないと省略していい理由が分からないですし。
可読性を考えると、省略しないことを勧めます。PageViewControllerの有能な点を理解して、活かした実装にしましょう。
補足:たまにPageView完了時にAfterPage呼び出されない
First -> Secondに遷移した際に、Thirdを読み込まないケースが自分の場合ありました。
フリックの勢いのせいかなwくらいしか検討がつかなくて、
原因はあんまり良くわかっていないので、知っている人いたら教えてくださいm(__)m参考サイト
https://swiswiswift.com/2018/06/21/page-view-controller-with-page-control/
https://qiita.com/Takeshi_Akutsu/items/dbf54df8e8a50e8ed5be
https://qiita.com/yajamon/items/e1754e7fc847b595c26a