質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
86.02%
Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Q&A

解決済

【SWIFT】UINavigationControllerのPush遷移にて、遷移先のUINavigatonControllerのカスタムビューレイアウトがUINavigationBarを考慮できない

beginner_Jiro
beginner_Jiro

総合スコア10

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

2回答

0グッド

0クリップ

298閲覧

投稿2022/10/27 09:39

編集2022/10/30 16:46

前提

メインコントローラー:UITabBarController
遷移元Controller:UIViewControllerA
遷移先Controller:UIViewControllerB
遷移先ViewControllerのカスタムビュー:CustomView

以上の構成より
下記の順序で画面遷移が行われております。

Swift

1///UITabBarController 2 let firstViewController = UIViewControllerA 3 let UINavigationController_0 = UINavigationController(rootViewController: firstViewController) 4 UINavigationController_0.modalPresentationStyle = .fullScreen 5 UINavigationController_0.tabBarItem = UITabBarItem(title: "tab1", image: .none, tag: 0)

Swift

1///UIViewControllerA 2 3 let UIVIEWCONTROLLERB = UIViewControllerB() 4 self.navigationController?.pushViewController(TARGETPROFILEVIEWCONTROLLER, animated: true) 5

実現したいこと

上記の画面遷移後
UIViewControllerB()ではカスタムビューとして自前で用意しているビューを設定しております。

Swift

1///UIViewControllerB 2 3 let CUSTOMVIEW = CustomView() 4 5 override func viewDidLayoutSubviews() { 6 7 self.view = CUSTOMVIEW 8 9 } 10

CUSTOMVIEW内ではコードレイアウトを行なっているのですが、一つ目のオブジェクトの基準となる
TopAnchorを設定する際、自身のTopAnchorと同じ値にした場合、NavigationControllerにViewが重なってしまいうまくレイアウトが行えませんでした(自身のViewのTopAncherは画面のTopAnchorなので上記のself.view = CUSTOMVIEWではNavigationControllerを考慮しない。当たり前ですが、、、)

Swift

1///CustomView() 2 ///これではカスタムビューに重なってしまう 3 object.topAnchor.constraint(equalTo: self.topAnchor).isActive = true 4

このようなことからなんとかカスタムビュー内でUInavigationControllerのナビゲーションバーのBottomの値を取得したいのですがどうしてもうまくいきません。
MVCモデルの採用を行なっているためにUINavigationController内でなるべくレイアウトの設定は行いたくないというのがありますのでUIVIEW内で完結できる方法がありましたらご教授いただけますと幸いです。

試したこと

■CustomViewのイニシャライズでnavigationBarのAnchorの値を渡す
⇨こちらカスタムビュー内のoverride init内でSuper.initにて継承基のframe初期化を行っていることの影響で
Designatedイニシャライザ及びConvenienceイニシャライザの実装ができませんでした

■CustomViewインスタンス後にプロパティオブザーバを実装し、navigationBarのAnchorの値をUIViewControllerBから渡したらレイアウトの処理が行われるようにする
⇨こちらは階層が異なるNavigationBarの値を使用することは禁止されているようで値が入ったプロパティをレイアウト時に使用する段階でエラーが発生してしまいました

■UIViewControllerBのビュー設定で、CustomViewのFrameを計算(UIViewControllerBにいるのでNavigationBarの値が取れる)してからAddSubViewを行い、設定する
⇨こちらはAddsubViewにした場合UITabBarControllerのタブが表示されてしまう

どうにかスマートに行う方法はありませんでしょうか。
そもそも、UINavigationControllerでPush遷移した先のViewがカスタムビューという状況はいくらでもあると思うのですがネットで調べてもStoryBoardで実装しているものばかりでコードレイアウトを行なっているものがなく、解決には至りませんでした。

試したことの三つ目にあるFrame計算の方法にて表示されているタブを非表示にする処理をUIViewControllerBにて行う手もあるのですがそもそもの画面領域が狭まってしまう(タブ領域を使用できない)というのがありますのでなるべく使いたくはありません。
どなたかご回答いただけますでしょうか。お願いいたします。

追記①

TakeOne様にて説明不足の旨ご指摘いただきましたので追記させていただきます。
コメント及びご指摘誠にありがとうございます。

Swift

1///MainTabBarController() 2import Foundation 3import UIKit 4 5final class MainTabBarController: UITabBarController { 6 7 override func viewDidLoad() { 8 super.viewDidLoad() 9 setupTab() 10 } 11 12 func setupTab() { 13/// タブにViewControllerを設定 14 let firstViewController = UIViewControllerA() 15 let UINavigationController_0 = UINavigationController(rootViewController: firstViewController) 16 UINavigationController_0.modalPresentationStyle = .fullScreen 17 UINavigationController_0.tabBarItem = UITabBarItem(title: "tab1", image: .none, tag: 0) 18 19 viewControllers = [UINavigationController_0] 20 } 21}

Swift

1///UIViewControllerA() 2import Foundation 3import UIKit 4 5class UIViewControllerA:UIViewController, UIVIEWADelegateProtcol { 6 7 override func viewDidLoad() { 8 let UIVIEWA = UIVIEWA() 9 self.view = UIVIEWA 10 self.view.backgroundColor = .black 11 UIVIEWA.delegate = self 12 13 } 14 15 func transitionbuttonTappedAction() { 16 let UIVIEWCONTROLLERB = UIViewControllerB() 17 self.navigationController?.pushViewController(UIVIEWCONTROLLERB, animated: true) 18 } 19} 20

Swift

1///UIVIEWA() 2 3import Foundation 4import UIKit 5 6protocol UIVIEWADelegateProtcol:AnyObject { 7 func transitionbuttonTappedAction() 8} 9 10 11class UIVIEWA:UIView { 12 13 ///変数宣言 14 weak var delegate:UIVIEWADelegateProtcol? 15 16 override init(frame: CGRect) { 17 super.init(frame: frame) 18 self.backgroundColor = .black 19 autoLayoutSetUp() 20 autoLayout() 21 } 22//※初期化処理※ 23 required init?(coder: NSCoder) { 24 fatalError("init(coder:) has not been implemented") 25 } 26 27 ///遷移ボタン 28 let transitionbutton:UIButton = { 29 let returnButton = UIButton() 30 returnButton.tag = 1 31 returnButton.backgroundColor = .white 32 returnButton.addTarget(self, action: #selector(butttonClicked(_:)), for: UIControl.Event.touchUpInside) 33 return returnButton 34 }() 35 36 ///遷移押下された際の挙動 37 @objc func butttonClicked(_ sender: UIButton) { 38 if let delegate = delegate { 39 self.delegate?.transitionbuttonTappedAction() 40 } 41 } 42 43 func autoLayoutSetUp() { 44 ///各オブジェクトをViewに追加 45 addSubview(transitionbutton) 46 47 ///UIオートレイアウトと競合させない処理 48 transitionbutton.translatesAutoresizingMaskIntoConstraints = false 49 } 50 51 func autoLayout() { 52///適当なレイアウト。押せればOK 53 transitionbutton.widthAnchor.constraint(equalTo: self.widthAnchor).isActive = true 54 transitionbutton.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 0.05).isActive = true 55 transitionbutton.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 100).isActive = true 56 transitionbutton.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true 57 } 58}

Swift

1import Foundation 2import UIKit 3 4class UIViewControllerB:UIViewController{ 5 6 override func viewDidLoad() { 7 let UIVIEWB = UIVIEWB() 8 self.view = UIVIEWB 9 self.view.backgroundColor = .black 10 } 11}

Swift

1import Foundation 2import UIKit 3 4class UIVIEWB:UIView { 5 6 override init(frame: CGRect) { 7 super.init(frame: frame) 8 self.backgroundColor = .black 9 autoLayoutSetUp() 10 autoLayout() 11 } 12//※初期化処理※ 13 required init?(coder: NSCoder) { 14 fatalError("init(coder:) has not been implemented") 15 } 16 17 ///適当なオブジェクト 18 let tempButton:UIButton = { 19 let returnButton = UIButton() 20 returnButton.backgroundColor = .white 21 return returnButton 22 }() 23 24 func autoLayoutSetUp() { 25 ///各オブジェクトをViewに追加 26 addSubview(tempButton) 27 28 ///UIオートレイアウトと競合させない処理 29 tempButton.translatesAutoresizingMaskIntoConstraints = false 30 } 31 32 func autoLayout() { 33_**ここが問題の箇所**_ 34///ここでのtempButtonをUINavigationControllerのバーの下に配置したいのだが、 35///UINavigationContoller.navigationController?.navigationBar.bottomAnchorの値を持ってくることができない。試したこと一覧は全てダメでした。 36 tempButton.topAnchor.constraint(equalTo: self.topAnchor).isActive = true 37 tempButton.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 0.05).isActive = true 38 tempButton.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 100).isActive = true 39 tempButton.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true 40 } 41}

以上になります。問題の箇所はUIViewBのtempButtonのレイアウト部分になります。
よろしくお願い申し上げます。

以下のような質問にはグッドを送りましょう

  • 質問内容が明確
  • 自分も答えを知りたい
  • 質問者以外のユーザにも役立つ

グッドが多くついた質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

気になる質問をクリップする

クリップした質問は、後からいつでもマイページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

下記のような質問は推奨されていません。

  • 間違っている
  • 質問になっていない投稿
  • スパムや攻撃的な表現を用いた投稿

適切な質問に修正を依頼しましょう。

TakeOne

2022/10/29 07:18 編集

コードだけでViewを表示しようとしている割には、説明されているコードが断片的で、どのような状況になっているのかよくわかりませんでした。断片的に見えているコードは、viewDidLayoutSubviewsでself.viewをCUSTOMIEWに差し替えようとしたりしていて、おかしなことをしているように見えます。AutoLayoutの設定も断片的で、topAnchorしか指定していないのだったら、AutoLayoutの設定不足でAutoLayoutが効いていないだけのようにも見えます。問題を再現できる簡単なプロジェクトを作成し、そのプロジェクトのコード全体を見せてもらったら何かアドバイスできるかもしれません。
beginner_Jiro

2022/10/30 17:38

TakeOne様 コメント並びにご指摘いただきましてありがとうございます。 最低限のコードとして再現プロジェクトを作成いたしました。(30分くらいで作ったのでコメント等雑な部分があります申し訳ありません) こちら、追記させていただきましたのでご確認いただけますでしょうか。 また、自己解答として別途コードを記載しておりますのでそちらもお手隙でしたらご確認いただけましたら幸いです。 以上になります。よろしくお願いいたします。

回答2

0

ベストアンサー

コードをコピペして内容を確認したところ、UIVIEWBのautoLayoutの設定でtempButtonのtopAnchorに対して

swift

1tempButton.topAnchor.constraint(equalTo: self.topAnchor).isActive = true

swift

1tempButton.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 100).isActive = true

の2つの制約が設定されていて、この設定に対して次のようなエラーメッセージが表示されています。

[LayoutConstraints] Unable to simultaneously satisfy constraints. Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. ( "<NSLayoutConstraint:0x6000018c1270 V:|-(0)-[UIButton:0x15d107aa0] (active, names: '|':NavTest.UIVIEWB:0x15d1078e0 )>", "<NSLayoutConstraint:0x6000018c10e0 UIButton:0x15d107aa0.top == UILayoutGuide:0x6000002fc0e0'UIViewSafeAreaLayoutGuide'.top + 100 (active)>", "<NSLayoutConstraint:0x6000018c1220 'UIViewSafeAreaLayoutGuide-top' V:|-(0)-[UILayoutGuide:0x6000002fc0e0'UIViewSafeAreaLayoutGuide'] (active, names: '|':NavTest.UIVIEWB:0x15d1078e0 )>" ) Will attempt to recover by breaking constraint <NSLayoutConstraint:0x6000018c10e0 UIButton:0x15d107aa0.top == UILayoutGuide:0x6000002fc0e0'UIViewSafeAreaLayoutGuide'.top + 100 (active)> Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger. The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKitCore/UIView.h> may also be helpful.

矛盾した2つの制約が設定されているのでsafeAreaLayoutGuide.topAnchor+100の方の制約は削除したと上記エラーメッセージでは言っています。

NavigationController配下で表示するViewControllerの中で、NavigationBarの下に配置したい場合は、safeAreaLayoutGuideを使って位置決めすればいいです。
なので、

swift

1tempButton.topAnchor.constraint(equalTo: self.topAnchor).isActive = true

を削除して、

swift

1tempButton.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 100).isActive = true

の方を残せば、NavigationBarの下100ptから表示されます。
NavigationBarの直下に表示したいのであれば、, constant: 100を削除して

swift

1tempButton.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor).isActive = true

を設定すればいいです。

あと、質問とは別に気になった点として、
UIViewControllerAのviewDidLoadの中でUIVIEWAインスタンスを作成して、それをself.viewに設定していますが、ViewControllerに表示するルートのビュー(self.view)を自分で作成してセットしたい時は、viewDidLoadではなくてloadViewで実施すべきです。または、viewDidLoadでやるなら、ルートのビュー(self.view)にUIVIEWAをaddSubviewで追加する形で実装すべきです。
(UIViewControllerBとUIVIEWBも同様です。)

swift

1 override func loadView() { 2 let UIVIEWA = UIVIEWA() 3 self.view = UIVIEWA 4 self.view.backgroundColor = .black 5 UIVIEWA.delegate = self 6 }

外見え的にはloadViewでやってもviewDidLoadでやっても変わらないように見えますが、ルートのビュー(self.view)に対して、UIViewControllerの内部で自動的にAutoLayoutの制約を設定しており、viewDidLoadでルートのビュー(self.view)を入れ替えると、その制約が消えてしまいます。そして、それがどこか思わぬところに影響する可能性があると思います。loadViewでself.viewを更新すれば、そこで設定したviewに対して内部でAutoLayoutが設定されます。Debug View Hierarchyを使って、ルートのビューに対してシステム内部でどのような制約を設定しているか確認しておくことをお勧めします。

投稿2022/10/31 02:42

TakeOne

総合スコア6210

良いと思った回答にはグッドを送りましょう。
グッドが多くついた回答ほどページの上位に表示されるので、他の人が素晴らしい回答を見つけやすくなります。

下記のような回答は推奨されていません。

  • 間違っている回答
  • 質問の回答になっていない投稿
  • スパムや攻撃的な表現を用いた投稿

このような回答には修正を依頼しましょう。

回答へのコメント

beginner_Jiro

2022/10/31 06:37

TakeOne様 この度は回答いただきありがとうございました。 safeAreaLayoutGuideですね。目から鱗です。ありがとうございます!本当に助かりました。 すぐに実装したいと思います。 加えてViewの差し替えについてのアドバイスもありがとうございます。 ライフサイクルについての知識がまだまだ乏しく、Debug View Hierarchyというワードも初聴でしたの 後ほど詳しく調べて制約に矛盾がないか調べてみます。 本当にありがとうございました。

0

ご指摘いただきました方々ありがとうございます。

一応望み通りの実装とすることができましたので自己回答として追記させていただきます。
遷移前のViewControllerのviewDidLayoutSubviews()内にてnavigationBarのMax Yの値をFrameの値として設定することで問題なくNavigationBarを考慮する作りとすることができました。
ただ、navigationBarのオプショナルの問題がありますのでここは追々修正してく所存です。
また、下記に追記で記載したプロジェクトに追記した部分を記載させていただきます。

Swift

1///UIViewControllerB 2import Foundation 3import UIKit 4 5class UIViewControllerB:UIViewController{ 6 7 override func viewDidLoad() { 8 let UIVIEWB = UIVIEWB() 9 self.view = UIVIEWB 10 self.view.backgroundColor = .black 11 12 } 13 14 override func viewDidLayoutSubviews() { 15 guard let navigationBarFrameMaxY = self.navigationController?.navigationBar.frame.maxY else { 16 return 17 } 18 self.view.frame = CGRect(x: 0, y: navigationBarFrameMaxY, width: self.view.frame.size.width, height: self.view.frame.size.height) 19 } 20 21}

Swift

1///UIViewB 2 3import Foundation 4import UIKit 5 6class UIVIEWB:UIView { 7 8 override init(frame: CGRect) { 9 super.init(frame: frame) 10 self.backgroundColor = .black 11 autoLayoutSetUp() 12 autoLayout() 13 } 14//※初期化処理※ 15 required init?(coder: NSCoder) { 16 fatalError("init(coder:) has not been implemented") 17 } 18 19 ///適当なオブジェクト 20 let tempButton:UIButton = { 21 let returnButton = UIButton() 22 returnButton.backgroundColor = .white 23 return returnButton 24 }() 25 26 27 func autoLayoutSetUp() { 28 ///各オブジェクトをViewに追加 29 addSubview(tempButton) 30 31 ///UIオートレイアウトと競合させない処理 32 tempButton.translatesAutoresizingMaskIntoConstraints = false 33 } 34 35 func autoLayout() { 36///フレームにてYの値を設定しているのでオブジェクトのTopはSelfのTopとイコールでOK 37 tempButton.topAnchor.constraint(equalTo: self.topAnchor).isActive = true 38 tempButton.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 0.05).isActive = true 39 tempButton.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 100).isActive = true 40 tempButton.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true 41 } 42}

また、これより安全性の高い、可読性の高い、コーディング規約として違反のない
記述方法がありましたら適宜ご回答いただけましたら幸いです
よろしくお願いいたします。

投稿2022/10/30 17:15

編集2022/10/30 17:16
beginner_Jiro

総合スコア10

良いと思った回答にはグッドを送りましょう。
グッドが多くついた回答ほどページの上位に表示されるので、他の人が素晴らしい回答を見つけやすくなります。

下記のような回答は推奨されていません。

  • 間違っている回答
  • 質問の回答になっていない投稿
  • スパムや攻撃的な表現を用いた投稿

このような回答には修正を依頼しましょう。

回答へのコメント

TakeOne

2022/10/31 02:47 編集

確認しました。AutoLayoutを正しく設定すれば、viewDidLayoutSubviewsでframeを操作する処理は不要です。ちなみに、viewDidLayoutSubviewsでframeを操作すると、操作したviewのAutoLayout設定は無効になります(正確にはAutoLayoutによるレイアウトを実施した後、frameで指定した位置に強制的に移動となります)。コード等を貼り付けるため、別回答として入れさせて頂きました。

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
86.02%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問

同じタグがついた質問を見る

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています