Last active
July 18, 2018 12:32
-
-
Save filletofish/b56600e9e661aa6e49cc0a56a98b37a3 to your computer and use it in GitHub Desktop.
Constraint that can be updated with safe area but only once
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
/// Work around problem described here: https://stackoverflow.com/questions/47223680/safe-area-changes-in-parent-vc-when-presenting-modally-vc-in-landscape | |
/// When presenting screen with another orientation safe area changes on first screen | |
/// that affects constraints and all layout that depends on safe area. | |
/// To avoid this bug one should use UCFixedSafeAreaLayoutConstraint that can be updated with safe | |
/// area inset using `updateAllFixedSafeAreaConstraints(newSafeAreaInsets:)` in UIViewController | |
/// | |
/// | |
/// | |
/// Custom constraint that preserves constant value when safe area changes. | |
/// One should attach it to corresponding view and it's superView, not safeAreaLayoutGuide! | |
/// | |
/// To set initial safe Area inset `notifyOnSafeAreaChange` should be called in | |
/// `UIViewController` `viewSafeAreaInsetsDidChange()`. Take a look at protocol | |
/// `FixedSafeAreaConstraintsUpdating` designed to update all `UCFixedSafeAreaLayoutConstraint` | |
/// at once. The protocol has a default implementation for `UIViewController` | |
/// | |
/// Example: | |
/// ``` | |
/// override func viewSafeAreaInsetsDidChange() { | |
/// super.viewSafeAreaInsetsDidChange() | |
/// self.updateAllFixedSafeAreaConstraints(newSafeArea: view.safeAreaInsets) | |
/// } | |
/// ``` | |
class FixedSafeAreaLayoutConstraint: NSLayoutConstraint { | |
private var hasSetSafeArea: Bool = false | |
/// Updates `constraint` with changed Safe Area Insets. | |
/// If it `constraint` has not updated before with not zero edge insets, then | |
/// safe area inset value would be added to `self.constant`, depending on | |
/// first and second `attribute`. | |
/// | |
/// If constraint connects left and left or trailing and trailing then `safeAreaInsets.left` | |
/// will be used as a additive value. Same applies to other attribute types. | |
/// | |
/// | |
/// | |
/// - Parameter safeAreaInsets: new safe area insets value | |
func notifyOnSafeAreaChange(_ safeAreaInsets: UIEdgeInsets) { | |
guard hasSetSafeArea == false else { | |
return | |
} | |
guard safeAreaInsets != UIEdgeInsets.zero else { | |
return | |
} | |
hasSetSafeArea = true | |
switch (firstAttribute, secondAttribute){ | |
case (.left, .left), (.leading, .leading): | |
self.constant += direction * safeAreaInsets.left | |
case (.right, .right), (.trailing, .trailing): | |
self.constant += -direction * safeAreaInsets.right | |
case (.top, .top): | |
self.constant += direction * safeAreaInsets.top | |
case (.bottom, .bottom): | |
self.constant += -direction * safeAreaInsets.bottom | |
case (.width, .width): | |
let alignedMultiplier: CGFloat = direction == 1.0 ? multiplier : 1.0 | |
let left = safeAreaInsets.left * alignedMultiplier | |
let right = safeAreaInsets.right * alignedMultiplier | |
self.constant += -direction * (left + right) | |
case (.height, .height): | |
let alignedMultiplier: CGFloat = direction == 1.0 ? multiplier : 1.0 | |
let top = safeAreaInsets.top * alignedMultiplier | |
let bottom = safeAreaInsets.bottom * alignedMultiplier | |
self.constant += -direction*(top + bottom) | |
default: | |
break | |
} | |
} | |
private var direction: CGFloat { | |
if let viewFirst = self.firstItem as? UIView, | |
let viewSecond = self.secondItem as? UIView { | |
for superView in sequence(first: viewFirst, next: { $0.superview }) { | |
if superView == viewSecond { | |
return 1.0 | |
} | |
} | |
return -1.0 | |
} else { | |
return 1.0 | |
} | |
} | |
} | |
extension UIViewController { | |
/// Implementation for UIViewController which updates all UCFixedSafeAreaLayoutConstraint in self.view. | |
/// Example: | |
/// ``` | |
/// override func viewSafeAreaInsetsDidChange() { | |
/// super.viewSafeAreaInsetsDidChange() | |
/// self.updateAllFixedSafeAreaConstraints(newSafeArea: view.safeAreaInsets) | |
/// } | |
/// ``` | |
/// | |
/// Work around problem described here: https://stackoverflow.com/questions/47223680/safe-area-changes-in-parent-vc-when-presenting-modally-vc-in-landscape | |
/// When presenting screen with another orientation safe area changes on first screen | |
/// that affects constraints and all layout that depends on safe area. | |
/// To avoid this bug one should use UCFixedSafeAreaLayoutConstraint that can be updated with safe | |
/// area inset using `updateAllFixedSafeAreaConstraints(newSafeAreaInsets:)` in UIViewController | |
func updateAllFixedSafeAreaConstraints(newSafeArea: UIEdgeInsets) { | |
for case let fixedSafeAreaConstraint as UCFixedSafeAreaLayoutConstraint in self.view.constraints { | |
fixedSafeAreaConstraint.notifyOnSafeAreaChange(newSafeArea) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment