Skip to content

Instantly share code, notes, and snippets.

@raulriera
Last active February 12, 2023 21:54
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save raulriera/64b91028dde631e6354114f12d69f911 to your computer and use it in GitHub Desktop.
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
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
}
}
@raulriera
Copy link
Author

Updated the implementation to fix the iOS 11 problems

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment