Skip to content

Instantly share code, notes, and snippets.

@filletofish
Last active July 18, 2018 12:32
Show Gist options
  • Save filletofish/b56600e9e661aa6e49cc0a56a98b37a3 to your computer and use it in GitHub Desktop.
Save filletofish/b56600e9e661aa6e49cc0a56a98b37a3 to your computer and use it in GitHub Desktop.
Constraint that can be updated with safe area but only once
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