import Foundation import UIKit import Display import AsyncDisplayKit import Postbox import TelegramCore import SwiftSignalKit import AccountContext import TelegramPresentationData import PresentationDataUtils import ComponentFlow import ViewControllerComponent import MultilineTextComponent import BundleIconComponent import SolidRoundedButtonComponent import Markdown private final class LimitScreenComponent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment let context: AccountContext let subject: LimitScreen.Subject let proceed: () -> Void init(context: AccountContext, subject: LimitScreen.Subject, proceed: @escaping () -> Void) { self.context = context self.subject = subject self.proceed = proceed } static func ==(lhs: LimitScreenComponent, rhs: LimitScreenComponent) -> Bool { if lhs.context !== rhs.context { return false } if lhs.subject != rhs.subject { return false } return true } final class State: ComponentState { private let context: AccountContext init(context: AccountContext) { self.context = context super.init() } } func makeState() -> State { return State(context: self.context) } static var body: Body { let icon = Child(BundleIconComponent.self) let title = Child(MultilineTextComponent.self) let text = Child(MultilineTextComponent.self) let button = Child(SolidRoundedButtonComponent.self) let cancel = Child(Button.self) return { context in let environment = context.environment[ViewControllerComponentContainer.Environment.self].value let component = context.component let theme = environment.theme let strings = environment.strings let topInset: CGFloat = 34.0 + 38.0 let sideInset: CGFloat = 16.0 + environment.safeInsets.left let textSideInset: CGFloat = 24.0 + environment.safeInsets.left let icon = icon.update( component: BundleIconComponent( name: "Premium/Tmp", tintColor: nil ), availableSize: CGSize(width: context.availableSize.width, height: CGFloat.greatestFiniteMagnitude), transition: .immediate ) let title = title.update( component: MultilineTextComponent( text: .plain(NSAttributedString(string: "Limit Reached", font: Font.semibold(17.0), textColor: theme.actionSheet.primaryTextColor, paragraphAlignment: .center)), horizontalAlignment: .center, maximumNumberOfLines: 1 ), availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude), transition: .immediate ) let textFont = Font.regular(15.0) let boldTextFont = Font.semibold(15.0) let textColor = theme.actionSheet.secondaryTextColor let string: String switch component.subject { case .chatsInFolder: string = "" case .folders: string = "" case .pins: string = strings.DialogList_ExtendedPinLimitError("\(5)", "\(10)").string } let attributedText = parseMarkdownIntoAttributedString(string, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: textColor), linkAttribute: { _ in return nil })) let text = text.update( component: MultilineTextComponent( text: .plain(attributedText), horizontalAlignment: .center, maximumNumberOfLines: 0, lineSpacing: 0.1 ), availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height), transition: .immediate ) let button = button.update( component: SolidRoundedButtonComponent( title: "Increase Limit", theme: SolidRoundedButtonComponent.Theme( backgroundColor: .black, backgroundColors: [UIColor(rgb: 0x407af0), UIColor(rgb: 0x9551e8), UIColor(rgb: 0xbf499a), UIColor(rgb: 0xf17b30)], foregroundColor: .white ), font: .bold, fontSize: 17.0, height: 50.0, cornerRadius: 10.0, gloss: false, iconName: "Premium/X2", iconPosition: .right, action: { [weak component] in guard let component = component else { return } component.proceed() } ), availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0), transition: context.transition ) let cancel = cancel.update(component: Button( content: AnyComponent(Text(text: strings.Common_Cancel, font: Font.regular(17.0), color: theme.actionSheet.controlAccentColor)), action: { } ), availableSize: context.availableSize, transition: context.transition) let width = context.availableSize.width context.add(icon .position(CGPoint(x: width / 2.0, y: 57.0)) ) context.add(title .position(CGPoint(x: width / 2.0, y: topInset + 39.0)) ) context.add(text .position(CGPoint(x: width / 2.0, y: topInset + 101.0)) ) let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: topInset + 76.0 + text.size.height + 27.0), size: button.size) context.add(button .position(CGPoint(x: buttonFrame.midX, y: buttonFrame.midY)) ) context.add(cancel .position(CGPoint(x: width / 2.0, y: topInset + 76.0 + text.size.height + 20.0 + button.size.height + 40.0)) ) let contentSize = CGSize(width: context.availableSize.width, height: topInset + title.size.height + text.size.height) return contentSize } } } public class LimitScreen: ViewController { final class Node: ViewControllerTracingNode, UIScrollViewDelegate, UIGestureRecognizerDelegate { private var presentationData: PresentationData private weak var controller: LimitScreen? private let component: AnyComponent private let theme: PresentationTheme? let dim: ASDisplayNode let wrappingView: UIView let containerView: UIView let scrollView: UIScrollView let hostView: ComponentHostView private(set) var isExpanded = false private var panGestureRecognizer: UIPanGestureRecognizer? private var panGestureArguments: (topInset: CGFloat, offset: CGFloat, scrollView: UIScrollView?, listNode: ListView?)? private var currentIsVisible: Bool = false private var currentLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)? fileprivate var temporaryDismiss = false init(context: AccountContext, controller: LimitScreen, component: AnyComponent, theme: PresentationTheme?) { self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.controller = controller self.component = component self.theme = theme self.dim = ASDisplayNode() self.dim.alpha = 0.0 self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.25) self.wrappingView = UIView() self.containerView = UIView() self.scrollView = UIScrollView() self.hostView = ComponentHostView() super.init() self.scrollView.delegate = self self.scrollView.showsVerticalScrollIndicator = false self.containerView.clipsToBounds = true self.containerView.backgroundColor = self.presentationData.theme.actionSheet.opaqueItemBackgroundColor self.addSubnode(self.dim) self.view.addSubview(self.wrappingView) self.wrappingView.addSubview(self.containerView) self.containerView.addSubview(self.scrollView) self.scrollView.addSubview(self.hostView) } override func didLoad() { super.didLoad() let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))) panRecognizer.delegate = self panRecognizer.delaysTouchesBegan = false panRecognizer.cancelsTouchesInView = true self.panGestureRecognizer = panRecognizer self.wrappingView.addGestureRecognizer(panRecognizer) self.dim.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:)))) self.controller?.navigationBar?.updateBackgroundAlpha(0.0, transition: .immediate) } @objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) { if case .ended = recognizer.state { self.controller?.dismiss(animated: true) } } override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { if let (layout, _) = self.currentLayout { if case .regular = layout.metrics.widthClass { return false } } return true } func scrollViewDidScroll(_ scrollView: UIScrollView) { let contentOffset = self.scrollView.contentOffset.y self.controller?.navigationBar?.updateBackgroundAlpha(min(30.0, contentOffset) / 30.0, transition: .immediate) } func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { if gestureRecognizer is UIPanGestureRecognizer && otherGestureRecognizer is UIPanGestureRecognizer { return true } return false } private var isDismissing = false func animateIn() { ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear).updateAlpha(node: self.dim, alpha: 1.0) let targetPosition = self.containerView.center let startPosition = targetPosition.offsetBy(dx: 0.0, dy: self.bounds.height) self.containerView.center = startPosition let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring) transition.animateView(allowUserInteraction: true, { self.containerView.center = targetPosition }, completion: { _ in }) } func animateOut(completion: @escaping () -> Void = {}) { self.isDismissing = true let positionTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut) positionTransition.updatePosition(layer: self.containerView.layer, position: CGPoint(x: self.containerView.center.x, y: self.bounds.height + self.containerView.bounds.height / 2.0), completion: { [weak self] _ in self?.controller?.dismiss(animated: false, completion: completion) }) let alphaTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut) alphaTransition.updateAlpha(node: self.dim, alpha: 0.0) if !self.temporaryDismiss { self.controller?.updateModalStyleOverlayTransitionFactor(0.0, transition: positionTransition) } } func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: Transition) { self.currentLayout = (layout, navigationHeight) if let controller = self.controller, let navigationBar = controller.navigationBar, navigationBar.view.superview !== self.wrappingView { self.containerView.addSubview(navigationBar.view) } self.dim.frame = CGRect(origin: CGPoint(x: 0.0, y: -layout.size.height), size: CGSize(width: layout.size.width, height: layout.size.height * 3.0)) var effectiveExpanded = self.isExpanded if case .regular = layout.metrics.widthClass { effectiveExpanded = true } let isLandscape = layout.orientation == .landscape let edgeTopInset = isLandscape ? 0.0 : self.defaultTopInset let topInset: CGFloat if let (panInitialTopInset, panOffset, _, _) = self.panGestureArguments { if effectiveExpanded { topInset = min(edgeTopInset, panInitialTopInset + max(0.0, panOffset)) } else { topInset = max(0.0, panInitialTopInset + min(0.0, panOffset)) } } else { topInset = effectiveExpanded ? 0.0 : edgeTopInset } transition.setFrame(view: self.wrappingView, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset), size: layout.size), completion: nil) let modalProgress = isLandscape ? 0.0 : (1.0 - topInset / self.defaultTopInset) self.controller?.updateModalStyleOverlayTransitionFactor(modalProgress, transition: transition.containedViewLayoutTransition) let clipFrame: CGRect if layout.metrics.widthClass == .compact { self.dim.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.25) if isLandscape { self.containerView.layer.cornerRadius = 0.0 } else { self.containerView.layer.cornerRadius = 10.0 } if #available(iOS 11.0, *) { if layout.safeInsets.bottom.isZero { self.containerView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] } else { self.containerView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner] } } if isLandscape { clipFrame = CGRect(origin: CGPoint(), size: layout.size) } else { let coveredByModalTransition: CGFloat = 0.0 var containerTopInset: CGFloat = 10.0 if let statusBarHeight = layout.statusBarHeight { containerTopInset += statusBarHeight } let unscaledFrame = CGRect(origin: CGPoint(x: 0.0, y: containerTopInset - coveredByModalTransition * 10.0), size: CGSize(width: layout.size.width, height: layout.size.height - containerTopInset)) let maxScale: CGFloat = (layout.size.width - 16.0 * 2.0) / layout.size.width let containerScale = 1.0 * (1.0 - coveredByModalTransition) + maxScale * coveredByModalTransition let maxScaledTopInset: CGFloat = containerTopInset - 10.0 let scaledTopInset: CGFloat = containerTopInset * (1.0 - coveredByModalTransition) + maxScaledTopInset * coveredByModalTransition let containerFrame = unscaledFrame.offsetBy(dx: 0.0, dy: scaledTopInset - (unscaledFrame.midY - containerScale * unscaledFrame.height / 2.0)) clipFrame = CGRect(x: containerFrame.minX, y: containerFrame.minY, width: containerFrame.width, height: containerFrame.height) } } else { self.dim.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.4) self.containerView.layer.cornerRadius = 10.0 let verticalInset: CGFloat = 44.0 let maxSide = max(layout.size.width, layout.size.height) let minSide = min(layout.size.width, layout.size.height) let containerSize = CGSize(width: min(layout.size.width - 20.0, floor(maxSide / 2.0)), height: min(layout.size.height, minSide) - verticalInset * 2.0) clipFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - containerSize.width) / 2.0), y: floor((layout.size.height - containerSize.height) / 2.0)), size: containerSize) } transition.setFrame(view: self.containerView, frame: clipFrame) transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(), size: clipFrame.size), completion: nil) let environment = ViewControllerComponentContainer.Environment( statusBarHeight: 0.0, navigationHeight: navigationHeight, safeInsets: UIEdgeInsets(top: layout.intrinsicInsets.top + layout.safeInsets.top, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom + layout.safeInsets.bottom, right: layout.safeInsets.right), isVisible: self.currentIsVisible, theme: self.theme ?? self.presentationData.theme, strings: self.presentationData.strings, controller: { [weak self] in return self?.controller } ) var contentSize = self.hostView.update( transition: transition, component: self.component, environment: { environment }, forceUpdate: true, containerSize: CGSize(width: clipFrame.size.width, height: 10000.0) ) contentSize.height = max(layout.size.height - navigationHeight, contentSize.height) transition.setFrame(view: self.hostView, frame: CGRect(origin: CGPoint(), size: contentSize), completion: nil) self.scrollView.contentSize = contentSize } private var didPlayAppearAnimation = false func updateIsVisible(isVisible: Bool) { if self.currentIsVisible == isVisible { return } self.currentIsVisible = isVisible guard let currentLayout = self.currentLayout else { return } self.containerLayoutUpdated(layout: currentLayout.layout, navigationHeight: currentLayout.navigationHeight, transition: .immediate) if !self.didPlayAppearAnimation { self.didPlayAppearAnimation = true self.animateIn() } } private var defaultTopInset: CGFloat { return 390.0 // guard let (layout, _) = self.currentLayout else{ // return 210.0 // } // if case .compact = layout.metrics.widthClass { // var factor: CGFloat = 0.2488 // if layout.size.width <= 320.0 { // factor = 0.15 // } // return floor(max(layout.size.width, layout.size.height) * factor) // } else { // return 210.0 // } } private func findScrollView(view: UIView?) -> (UIScrollView, ListView?)? { if let view = view { if let view = view as? UIScrollView { return (view, nil) } if let node = view.asyncdisplaykit_node as? ListView { return (node.scroller, node) } return findScrollView(view: view.superview) } else { return nil } } @objc func panGesture(_ recognizer: UIPanGestureRecognizer) { guard let (layout, navigationHeight) = self.currentLayout else { return } let isLandscape = layout.orientation == .landscape let edgeTopInset = isLandscape ? 0.0 : defaultTopInset switch recognizer.state { case .began: let point = recognizer.location(in: self.view) let currentHitView = self.hitTest(point, with: nil) var scrollViewAndListNode = self.findScrollView(view: currentHitView) if scrollViewAndListNode?.0.frame.height == self.frame.width { scrollViewAndListNode = nil } let scrollView = scrollViewAndListNode?.0 let listNode = scrollViewAndListNode?.1 let topInset: CGFloat if self.isExpanded { topInset = 0.0 } else { topInset = edgeTopInset } self.panGestureArguments = (topInset, 0.0, scrollView, listNode) case .changed: guard let (topInset, panOffset, scrollView, listNode) = self.panGestureArguments else { return } let visibleContentOffset = listNode?.visibleContentOffset() let contentOffset = scrollView?.contentOffset.y ?? 0.0 var translation = recognizer.translation(in: self.view).y var currentOffset = topInset + translation let epsilon = 1.0 if case let .known(value) = visibleContentOffset, value <= epsilon { if let scrollView = scrollView { scrollView.bounces = false scrollView.setContentOffset(CGPoint(x: 0.0, y: 0.0), animated: false) } } else if let scrollView = scrollView, contentOffset <= -scrollView.contentInset.top + epsilon { scrollView.bounces = false scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false) } else if let scrollView = scrollView { translation = panOffset currentOffset = topInset + translation if self.isExpanded { recognizer.setTranslation(CGPoint(), in: self.view) } else if currentOffset > 0.0 { scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false) } } self.panGestureArguments = (topInset, translation, scrollView, listNode) if !self.isExpanded { if currentOffset > 0.0, let scrollView = scrollView { scrollView.panGestureRecognizer.setTranslation(CGPoint(), in: scrollView) } } var bounds = self.bounds if self.isExpanded { bounds.origin.y = -max(0.0, translation - edgeTopInset) } else { bounds.origin.y = -translation } bounds.origin.y = min(0.0, bounds.origin.y) self.bounds = bounds self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate) case .ended: guard let (currentTopInset, panOffset, scrollView, listNode) = self.panGestureArguments else { return } self.panGestureArguments = nil let visibleContentOffset = listNode?.visibleContentOffset() let contentOffset = scrollView?.contentOffset.y ?? 0.0 let translation = recognizer.translation(in: self.view).y var velocity = recognizer.velocity(in: self.view) if self.isExpanded { if case let .known(value) = visibleContentOffset, value > 0.1 { velocity = CGPoint() } else if case .unknown = visibleContentOffset { velocity = CGPoint() } else if contentOffset > 0.1 { velocity = CGPoint() } } var bounds = self.bounds if self.isExpanded { bounds.origin.y = -max(0.0, translation - edgeTopInset) } else { bounds.origin.y = -translation } bounds.origin.y = min(0.0, bounds.origin.y) scrollView?.bounces = true let offset = currentTopInset + panOffset let topInset: CGFloat = edgeTopInset var dismissing = false if bounds.minY < -60 || (bounds.minY < 0.0 && velocity.y > 300.0) || (self.isExpanded && bounds.minY.isZero && velocity.y > 1800.0) { self.controller?.dismiss(animated: true, completion: nil) dismissing = true } else if self.isExpanded { if velocity.y > 300.0 || offset > topInset / 2.0 { self.isExpanded = false if let listNode = listNode { listNode.scroller.setContentOffset(CGPoint(), animated: false) } else if let scrollView = scrollView { scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false) } let distance = topInset - offset let initialVelocity: CGFloat = distance.isZero ? 0.0 : abs(velocity.y / distance) let transition = ContainedViewLayoutTransition.animated(duration: 0.45, curve: .customSpring(damping: 124.0, initialVelocity: initialVelocity)) self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: Transition(transition)) } else { self.isExpanded = true self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: Transition(.animated(duration: 0.3, curve: .easeInOut))) } } else if (velocity.y < -300.0 || offset < topInset / 2.0) { if velocity.y > -2200.0 && velocity.y < -300.0, let listNode = listNode { DispatchQueue.main.async { listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) } } let initialVelocity: CGFloat = offset.isZero ? 0.0 : abs(velocity.y / offset) let transition = ContainedViewLayoutTransition.animated(duration: 0.45, curve: .customSpring(damping: 124.0, initialVelocity: initialVelocity)) self.isExpanded = true self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: Transition(transition)) } else { if let listNode = listNode { listNode.scroller.setContentOffset(CGPoint(), animated: false) } else if let scrollView = scrollView { scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false) } self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: Transition(.animated(duration: 0.3, curve: .easeInOut))) } if !dismissing { var bounds = self.bounds let previousBounds = bounds bounds.origin.y = 0.0 self.bounds = bounds self.layer.animateBounds(from: previousBounds, to: self.bounds, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue) } case .cancelled: self.panGestureArguments = nil self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: Transition(.animated(duration: 0.3, curve: .easeInOut))) default: break } } func update(isExpanded: Bool, transition: ContainedViewLayoutTransition) { guard isExpanded != self.isExpanded else { return } self.isExpanded = isExpanded guard let (layout, navigationHeight) = self.currentLayout else { return } self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: Transition(transition)) } } var node: Node { return self.displayNode as! Node } private let context: AccountContext private let theme: PresentationTheme? private let component: AnyComponent private var isInitiallyExpanded = false private var currentLayout: ContainerViewLayout? public var pushController: (ViewController) -> Void = { _ in } public var presentController: (ViewController) -> Void = { _ in } public enum Subject { case folders case chatsInFolder case pins } public convenience init(context: AccountContext, subject: Subject) { self.init(context: context, component: LimitScreenComponent(context: context, subject: subject, proceed: {})) } private init(context: AccountContext, component: C, theme: PresentationTheme? = nil) where C.EnvironmentType == ViewControllerComponentContainer.Environment { self.context = context self.component = AnyComponent(component) self.theme = nil super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: context.sharedContext.currentPresentationData.with { $0 })) } required public init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } @objc private func cancelPressed() { self.dismiss(animated: true, completion: nil) } override open func loadDisplayNode() { self.displayNode = Node(context: self.context, controller: self, component: self.component, theme: self.theme) self.displayNodeDidLoad() } public override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { self.view.endEditing(true) if flag { self.node.animateOut(completion: { super.dismiss(animated: false, completion: {}) completion?() }) } else { super.dismiss(animated: false, completion: {}) completion?() } } override open func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) self.node.updateIsVisible(isVisible: true) } override open func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) self.node.updateIsVisible(isVisible: false) } override public func updateNavigationBarLayout(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { var navigationLayout = self.navigationLayout(layout: layout) var navigationFrame = navigationLayout.navigationFrame var layout = layout if case .regular = layout.metrics.widthClass { let verticalInset: CGFloat = 44.0 let maxSide = max(layout.size.width, layout.size.height) let minSide = min(layout.size.width, layout.size.height) let containerSize = CGSize(width: min(layout.size.width - 20.0, floor(maxSide / 2.0)), height: min(layout.size.height, minSide) - verticalInset * 2.0) let clipFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - containerSize.width) / 2.0), y: floor((layout.size.height - containerSize.height) / 2.0)), size: containerSize) navigationFrame.size.width = clipFrame.width layout.size = clipFrame.size } navigationFrame.size.height = 56.0 navigationLayout.navigationFrame = navigationFrame navigationLayout.defaultContentHeight = 56.0 layout.statusBarHeight = nil self.applyNavigationBarLayout(layout, navigationLayout: navigationLayout, additionalBackgroundHeight: 0.0, transition: transition) } override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { self.currentLayout = layout super.containerLayoutUpdated(layout, transition: transition) let navigationHeight: CGFloat = 56.0 self.node.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: Transition(transition)) } }