Last active
February 12, 2023 21:54
-
-
Save raulriera/64b91028dde631e6354114f12d69f911 to your computer and use it in GitHub Desktop.
Final implementation for the Medium article "The case of the disappearing titleView" https://medium.com/shopify-mobile/the-case-of-the-disappearing-titleview-%EF%B8%8F-e516ffd2fea2
This file contains hidden or 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 Foundation | |
final class ConcealingTitleView: UIView { | |
private let label = UILabel() | |
private var contentOffset: CGFloat = 0 { | |
didSet { | |
label.frame.origin.y = titleVerticalPositionAdjusted(by: contentOffset) | |
} | |
} | |
var text: String = "" { | |
didSet { | |
label.text = text | |
label.textColor = UIColor.white | |
label.font = UIFont.systemFont(ofSize: 17.0, weight: .semibold) | |
setNeedsLayout() | |
} | |
} | |
// MARK: Initializers | |
override init(frame: CGRect) { | |
super.init(frame: frame) | |
addSubview(label) | |
clipsToBounds = true | |
isUserInteractionEnabled = false | |
} | |
required init?(coder aDecoder: NSCoder) { | |
fatalError("init(coder:) has not been implemented") | |
} | |
// MARK: View lifecycle | |
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { | |
super.traitCollectionDidChange(previousTraitCollection) | |
sizeToFit() | |
} | |
override func sizeThatFits(_ size: CGSize) -> CGSize { | |
if #available(iOS 11.0, *) { | |
return super.sizeThatFits(size) | |
} else { | |
let height = (typedSuperview() as? UINavigationBar)?.bounds.height ?? 0.0 | |
let labelSize = label.sizeThatFits(CGSize(width: size.width, height: height)) | |
return CGSize(width: min(labelSize.width, size.width), height: height) | |
} | |
} | |
private func layoutSubviews_10() { | |
guard let navBar = typedSuperview() as? UINavigationBar else { return } | |
let center = convert(navBar.center, from: navBar) | |
let size = label.sizeThatFits(bounds.size) | |
let x = max(bounds.minX, center.x - size.width * 0.5) | |
let y: CGFloat | |
if contentOffset == 0 { | |
y = bounds.maxY | |
} else { | |
y = titleVerticalPositionAdjusted(by: contentOffset) | |
} | |
label.frame = CGRect(x: x, y: y, width: min(size.width, bounds.width), height: size.height) | |
} | |
private func layoutSubviews_11() { | |
let size = label.sizeThatFits(bounds.size) | |
let x: CGFloat | |
let y: CGFloat | |
x = bounds.midX - size.width * 0.5 | |
if contentOffset == 0 { | |
y = bounds.maxY | |
} else { | |
y = titleVerticalPositionAdjusted(by: contentOffset) | |
} | |
label.frame = CGRect(x: x, y: y, width: size.width, height: size.height) | |
} | |
override func layoutSubviews() { | |
super.layoutSubviews() | |
if #available(iOS 11.0, *) { | |
layoutSubviews_11() | |
} else { | |
layoutSubviews_10() | |
} | |
} | |
// MARK: | |
/// Using the UIScrollViewDelegate it moves the inner UILabel to move up and down following the scroll offset. | |
/// | |
/// - Parameters: | |
/// - scrollView: The scroll-view object in which the scrolling occurred. | |
/// - threshold: The minimum distance that must be scrolled before the title view will begin scrolling up into view | |
func scrollViewDidScroll(_ scrollView: UIScrollView, threshold: CGFloat = 0) { | |
contentOffset = scrollView.contentOffset.y - threshold | |
} | |
// MARK: Private | |
private func titleVerticalPositionAdjusted(by yOffset: CGFloat) -> CGFloat { | |
let midY = bounds.midY - label.bounds.height * 0.5 | |
return max(bounds.maxY - yOffset, midY).rounded() | |
} | |
} | |
private extension UIView { | |
func typedSuperview<T: UIView>() -> T? { | |
var parent = superview | |
while parent != nil { | |
if let view = parent as? T { | |
return view | |
} else { | |
parent = parent?.superview | |
} | |
} | |
return nil | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Updated the implementation to fix the iOS 11 problems