目次
前説
こんな感じでいい感じに動画コンテンツみたくインライン表示、フルスクリーン表示を切り替えたい。
前提条件
Xcode: 12.1
シミュレータ: iOS14系
というわけで作っていきます。
画面構成

流れをシーケンス図で表示するとこうなる
インライン -> フルスクリーン

フルスクリーン -> インライン

前準備1. それぞれのVCの回転制御について
今回の前提として、インライン用VCは回転不可で縦方向のみサポート。
override var shouldAutorotate: Bool {
return false
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .portrait
}
フルスクリーン用VCは回転可能で縦・横両方サポートとします。
override var shouldAutorotate: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .allButUpsideDown
}
前準備2. インライン用VCにおける、対象の要素についてのView構成
今回はインライン用VCにある要素を、そのままフルスクリーン用VCに渡して全画面表示にします。
なのでフルスクリーン表示時には、インライン用VCからその要素がなくなっている状態のですが、
その間も画面が崩れないようにする必要があります。
そのため、インライン用VCにおいては、対象の要素の親のUIView を一つ作り、
表示崩れしないように領域を確保しておきます。

では以下からいい感じにフルスクリーン表示する処理を記述していきます!
段階を追ってコードを細切れに書いていますが、最下部には必要なコードをまとめています。
フルスクリーン表示にするとき
1.透明のフルスクリーン用VCを表示し、見た目が変わらない位置に要素を配置する
まずインライン 表示用VCにてタップイベントなど適当なイベントのハンドラを作り、
そこをフルスクリーン表示用処理の入り口とします。
ここは今回の内容とは関係ないので割愛します。
処理を開始したらまず透明のフルスクリーン用VCを作成します。
let sampleFullScreenVC = self.storyboard?.instantiateViewController(withIdentifier: "SampleFullScreenVC") as! SampleFullScreenVC
sampleFullScreenVC.view.backgroundColor = .clear
フルスクリーン用VCに要素を渡し、後の位置調整やアニメーションの処理はそちらのVCで行うことにします。
フルスクリーン用VCに func animateToFullScreen(_ targetView: UIView)
というメソッドを用意しました。
present(sampleFullScreenVC, animated: false) { [weak self] in
guard let self = self else {
return
}
if let view = self.targetView {
sampleFullScreenVC.animateToFullScreen(view)
}
}
インライン用VCで必要なコードは以上です。
これ以降インライン -> フルスクリーン、フルスクリーン -> インライン の処理はすべて
フルスクリーン用VCに書いていきます。
さて、インライン用VCから要素が渡されたので、まずは見た目上の位置が変わらないよう
フルスクリーン用VC に貼り付けます。
func animateToFullScreen(_ targetView: UIView) {
// フルスクリーン -> インライン に戻るときに操作するので、要素をプロパティに保持する
self.targetView = targetView
// フルスクリーン -> インライン で元いたView に貼り戻すため、元々の親View をプロパティに保持する
self.originalSuperviewOfTargetView = targetView.superview
// 貼り付けたときの見た目上の位置が変わらないよう、frame を変換する
// また、フルスクリーン -> インライン で元の位置に戻すのに必要なため、
// 最初の位置を frame としてプロパティに保持しておく
self.initialTargetViewFrame = self.view.convert(
targetView.frame,
from: targetView.superview
)
if let frame = self.initialTargetViewFrame {
targetView.frame = frame
}
self.view.addSubview(targetView)
上のコメントにもあるようにフルスクリーン -> インラインに戻るときに、
元の位置や、貼り戻す親のUIView が必要なため、以下のようにプロパティを用意します。
private var targetView: UIView?
private var originalSuperviewOfTargetView: UIView?
private var initialTargetViewFrame: CGRect?
以上で、見た目は変わらないままで、要素をインラインVCからフルスクリーンVCに引き渡せました。
2.フルスクリーン用VCの背景色を黒くする
いまの段階ではフルスクリーン用VCの背景色が透明なため、
奥にあるインライン用VCが透けて見えています。
なので背景色を黒くするのですが、急に真っ暗になるとびっくりするので
アニメーションで徐々に黒くしていきます。
UIView.animate(
withDuration: 0.5,
animations: {
self.view.backgroundColor = .black
},
completion: { _ in
// この後、このcompletion 内で画面横向き & 全画面表示の処理をします
}
)
上のコードでは背景色が黒くなるのを待ってから、横向き & 全画面表示の処理を実行しようとしています。
なぜかと言うと、背景色を変えるのと同時に横向きにすると、
奥にいるインラインVCも横向きになってしまうのが少し透けて見えてしまうからです。
以下のように見えてしまい、かっこ悪いですね!
3.画面を強制的に横向きにしつつ、要素を全画面に表示する
あとは以下のコードを、上記UIView.animate メソッドの completion 内に書けばOKです。
// 端末を強制的に横向きにする
UIDevice.current.setValue(
UIInterfaceOrientation.landscapeRight.rawValue,
forKey: "orientation"
)
// 要素を全画面にする
UIView.animate(
withDuration: 0.5,
animations: {
if let superviewFrame = targetView.superview?.frame {
targetView.frame = CGRect(
x: 0.0,
y: 0.0,
width: superviewFrame.width,
height: superviewFrame.height
)
}
self.view.layoutIfNeeded()
}
)
以上でインライン表示 -> フルスクリーン表示の遷移は完了です!
一点注意として、要素に対してframe で位置や大きさを指定していますが、
これだと回転させると表示崩れを起こしてしまうかもしれません。
なのでAutoLayout で指定した方がいいとは思いますが、今回の主旨とは関係ないので割愛しています。
以降フルスクリーン表示 -> インライン表示に戻る時の処理も書きますが、
ここで書いた処理を逆にすればよいだけです。
インライン表示に戻るとき
1.画面を強制的に縦向きに戻しつつ、見た目上の元の位置に要素を戻す
以下のコードを書きます。
// 端末を強制的に縦向きに戻す
UIDevice.current.setValue(
UIInterfaceOrientation.portrait.rawValue,
forKey: "orientation"
)
// 見た目上の元の位置に要素を戻す
UIView.animate(
withDuration: 0.5,
animations: {
if let initialTargetViewFrame = self.initialTargetViewFrame {
self.targetView?.frame = CGRect(
x: initialTargetViewFrame.minX,
y: initialTargetViewFrame.minY,
width: initialTargetViewFrame.width,
height: initialTargetViewFrame.height
)
}
self.view.layoutIfNeeded()
},
completion: { _ in
// この後、このcompletion 内でフルスクリーン用VCの背景色を透明にします
}
)
元の位置に戻すために、フルスクリーン表示時の最初に保存しておいた frame を利用しています。
2.フルスクリーン用VCの背景色を透明にする
以下のコードを書きます。
特筆することはありません。
UIView.animate(
withDuration: 0.5,
animations: { [weak self] in
self?.view.backgroundColor = .clear
},
completion: { _ in
// この後、このcompletion 内で要素をインライン用VCに貼り直し、フルスクリーン用VCを消します
}
)
3.要素をインライン用VCに貼り直し、フルスクリーン用VCを消す
以下のコードを書きます。
こちらも特筆することはありません。
if let targetView = self.targetView {
// インライン用VCの元いた親View に貼り戻す
self.originalSuperviewOfTargetView?.addSubview(targetView)
// 貼り付けたときの見た目上の位置が変わらないよう、frame を変換する
if let initialTargetViewFrame = self.initialTargetViewFrame {
let frame = self.view.convert(
initialTargetViewFrame,
to: targetView.superview
)
targetView.frame = frame
}
}
self.dismiss(animated: false)
以上でできました!
コードまとめ
インライン用VC
class SamplePlayerAndCollectionVC: UIViewController {
override var shouldAutorotate: Bool {
return false
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .portrait
}
func openFullScreen() {
let sampleFullScreenVC = self.storyboard?.instantiateViewController(withIdentifier: "SampleFullScreenVC") as! SampleFullScreenVC
sampleFullScreenVC.view.backgroundColor = .clear
present(sampleFullScreenVC, animated: false) { [weak self] in
guard let self = self else {
return
}
if let view = self.targetView {
sampleFullScreenVC.animateToFullScreen(view)
}
}
}
}
フルスクリーン用VC
class SampleFullScreenVC: UIViewController {
private var targetView: UIView?
private var originalSuperviewOfTargetView: UIView?
private var initialTargetViewFrame: CGRect?
override var shouldAutorotate: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .allButUpsideDown
}
// インライン -> フルスクリーン
func animateToFullScreen(_ targetView: UIView) {
self.targetView = targetView
self.originalSuperviewOfTargetView = targetView.superview
self.initialTargetViewFrame = self.view.convert(
targetView.frame,
from: targetView.superview
)
if let frame = self.initialTargetViewFrame {
targetView.frame = frame
}
self.view.addSubview(targetView)
UIView.animate(
withDuration: 0.5,
animations: {
self.view.backgroundColor = .black
},
completion: { _ in
UIDevice.current.setValue(
UIInterfaceOrientation.landscapeRight.rawValue,
forKey: "orientation"
)
UIView.animate(
withDuration: 0.5,
animations: {
if let superviewFrame = targetView.superview?.frame {
targetView.frame = CGRect(
x: 0.0,
y: 0.0,
width: superviewFrame.width,
height: superviewFrame.height
)
}
self.view.layoutIfNeeded()
}
)
}
)
}
// フルスクリーン -> インライン
func animateToInline() {
UIDevice.current.setValue(
UIInterfaceOrientation.portrait.rawValue,
forKey: "orientation"
)
UIView.animate(
withDuration: 0.5,
animations: {
if let initialTargetViewFrame = self.initialTargetViewFrame {
self.targetView?.frame = CGRect(
x: initialTargetViewFrame.minX,
y: initialTargetViewFrame.minY,
width: initialTargetViewFrame.width,
height: initialTargetViewFrame.height
)
}
self.view.layoutIfNeeded()
},
completion: { _ in
UIView.animate(
withDuration: 0.5,
animations: { [weak self] in
self?.view.backgroundColor = .clear
},
completion: { _ in
if let targetView = self.targetView {
self.originalSuperviewOfTargetView?.addSubview(targetView)
if let initialTargetViewFrame = self.initialTargetViewFrame {
let frame = self.view.convert(
initialTargetViewFrame,
to: targetView.superview
)
targetView.frame = frame
}
}
self.dismiss(animated: false)
}
)
}
)
}
}
最後に
長くなってしまいましたが、段階を追っていけばシンプルな手順かなと思います。
以上です。お疲れ様でした!
「[iOS] ViewController を切り替えていい感じにフルスクリーン表示する」への1件のフィードバック