import UIKit @main final class AppDelegate: UIResponder, UIApplicationDelegate { func application( _ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions ) -> UISceneConfiguration { let configuration = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role) configuration.delegateClass = SceneDelegate.self return configuration } } final class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = scene as? UIWindowScene else { return } let window = UIWindow(windowScene: windowScene) self.window = window window.makeKeyAndVisible() install(.plain) } private func install(_ hierarchy: ViewController.Hierarchy) { let content = ViewController(hierarchy: hierarchy) { [weak self] hierarchy in self?.install(hierarchy) } switch hierarchy { case .plain: window?.rootViewController = content case .navigationController: let navigationController = UINavigationController(rootViewController: content) navigationController.setNavigationBarHidden(true, animated: false) window?.rootViewController = navigationController } } } /// Draws the bar layout guide for every edge at extents 22, 44 and 88 pt: /// left = yellow, top = blue, right = green, bottom = red. /// Use the center button to switch between a plain root view controller and one embedded in a /// UINavigationController (navigation bar hidden). Guide frames are logged on every layout pass. final class ViewController: UIViewController { enum Hierarchy: String, CaseIterable { case plain = "Plain root" case navigationController = "UINavigationController" } private let hierarchy: Hierarchy private let onSelectHierarchy: (Hierarchy) -> Void private var debugGuides: [(String, UILayoutGuide)] = [] private let hierarchyButton = UIButton(configuration: .filled()) init(hierarchy: Hierarchy, onSelectHierarchy: @escaping (Hierarchy) -> Void) { self.hierarchy = hierarchy self.onSelectHierarchy = onSelectHierarchy super.init(nibName: nil, bundle: nil) } @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .systemBackground installHierarchyButton() installBarLayoutGuideViews() } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() logLayout() } private func installHierarchyButton() { hierarchyButton.translatesAutoresizingMaskIntoConstraints = false hierarchyButton.configuration?.title = hierarchy.rawValue hierarchyButton.showsMenuAsPrimaryAction = true hierarchyButton.menu = UIMenu(children: Hierarchy.allCases.map { option in UIAction(title: option.rawValue, state: option == hierarchy ? .on : .off) { [onSelectHierarchy] _ in onSelectHierarchy(option) } }) view.addSubview(hierarchyButton) NSLayoutConstraint.activate([ hierarchyButton.centerXAnchor.constraint(equalTo: view.centerXAnchor), hierarchyButton.centerYAnchor.constraint(equalTo: view.centerYAnchor), ]) } private func installBarLayoutGuideViews() { let edges: [(UIRectEdge, String, UIColor)] = [ (.left, "left", .systemYellow), (.top, "top", .systemBlue), (.right, "right", .systemGreen), (.bottom, "bottom", .systemRed), ] for (edge, edgeName, color) in edges { for extent: CGFloat in [22, 44, 88] { let guide = view.layoutGuide(for: .bar(onEdge: edge, extent: extent)) if guide.owningView == nil { view.addLayoutGuide(guide) } debugGuides.append(("\(edgeName) \(Int(extent))", guide)) let guideView = UIView() guideView.translatesAutoresizingMaskIntoConstraints = false guideView.isUserInteractionEnabled = false guideView.backgroundColor = color.withAlphaComponent(0.2) view.insertSubview(guideView, belowSubview: hierarchyButton) NSLayoutConstraint.activate([ guideView.leadingAnchor.constraint(equalTo: guide.leadingAnchor), guideView.trailingAnchor.constraint(equalTo: guide.trailingAnchor), guideView.topAnchor.constraint(equalTo: guide.topAnchor), guideView.bottomAnchor.constraint(equalTo: guide.bottomAnchor), ]) } } } private func logLayout() { let regions = view.reservedRegions(kind: .occlusion, options: .includeInactive) var lines = [ "[BarLayoutGuideRepro] \(hierarchy.rawValue)", " view in window: \(view.convert(view.bounds, to: nil))", " view.safeAreaInsets: \(view.safeAreaInsets)", " window.safeAreaInsets: \(view.window?.safeAreaInsets ?? .zero)", " verticalBarEdge: \(String(describing: traitCollection.verticalBarEdge))", " occlusion regions: \(regions.count)", ] lines += regions.map { " frame=\($0.frame) margins=\($0.margins) active=\($0.isActive)" } lines += debugGuides.map { " \($0.0): \($0.1.layoutFrame)" } print(lines.joined(separator: "\n")) } }