Development environment:
- Swift Language Version: Swift 5
- Xcode: Version 10.3
- Deployment Target: 12.0
Step 1: Create the ViewController screen
We will create the ViewController screen as shown below:
Including the following components:
- ScrollView: Add top, leading, trailing and bottom constraint, especially top set constraint with Superview (constant = 0).
- View1: Add top, leading, trailing and bottom constraint with scrollView (constant = 0), with height = 1000 and width = View.width
- Image1: Add top, leading, trailing constraint with View1 and height = 300; ContentMode = Aspect Fill.
Step 2: Create a stretchy header effect
We declare outlets, variables and initial values:
1 2 3 4 5 6 7 8 9 10 11 12 13 | @IBOutlet private weak var scrollView: UIScrollView! @IBOutlet private weak var imageView: UIImageView! @IBOutlet private weak var imageHeightConstraint: NSLayoutConstraint! @IBOutlet private weak var imageTopConstraint: NSLayoutConstraint! private var originalHeight: CGFloat! override func viewDidLoad() { super.viewDidLoad() scrollView.contentInsetAdjustmentBehavior = .never scrollView.delegate = self originalHeight = 300 } |
The value of imageHeightConstraint varies based on the offset value.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | extension ViewController: UIScrollViewDelegate { func scrollViewDidScroll(_ scrollView: UIScrollView) { let offset = scrollView.contentOffset.y let defaultTop = CGFloat(0) var currentTop = defaultTop if scrollView == self.scrollView { if offset < 0 { // User scroll down currentTop = offset imageHeightConstraint.constant = originalHeight - offset } else { imageHeightConstraint.constant = originalHeight } imageTopConstraint.constant = currentTop } } } |
Inside:
- originalHeight is the initial value of the image height constraint.
- offset is the relative y coordinate value between the beginning of the viewport (contentView) and the beginning of the scrollView.
- defaultTop is the initial value of the image top constraint.
- currentTop is the current value of the image top constraint.
Step 3: Create a Blur Effect
1 2 3 4 5 6 7 8 9 | private var blurEffectView: UIVisualEffectView! private var animator: UIViewPropertyAnimator! override func viewDidLoad() { super.viewDidLoad() ... setupVisualEffectBlur() } |
We create the blurEffectView subview and set the constraint with imageView .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | private func setupVisualEffectBlur() { animator = UIViewPropertyAnimator(duration: 3.0, curve: .linear, animations: { [weak self] in guard let self = self else { return } let blurEffect = UIBlurEffect(style: .regular) self.blurEffectView = UIVisualEffectView(effect: blurEffect) self.imageView.addSubview(self.blurEffectView) self.setupConstraints() }) } // Setup blurEffectView's constraint to imageView private func setupConstraints() { blurEffectView.translatesAutoresizingMaskIntoConstraints = false blurEffectView.leadingAnchor.constraint(equalTo: imageView.leadingAnchor).isActive = true blurEffectView.trailingAnchor.constraint(equalTo: imageView.trailingAnchor).isActive = true blurEffectView.topAnchor.constraint(equalTo: imageView.topAnchor).isActive = true blurEffectView.bottomAnchor.constraint(equalTo: imageView.bottomAnchor).isActive = true } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | func scrollViewDidScroll(_ scrollView: UIScrollView) { let offset = scrollView.contentOffset.y let defaultTop = CGFloat(0) var currentTop = defaultTop if scrollView == self.scrollView { if offset < 0 { currentTop = offset imageHeightConstraint.constant = originalHeight - offset animator.fractionComplete = abs(offset) / 100 } else { imageHeightConstraint.constant = originalHeight animator.fractionComplete = 0 } imageTopConstraint.constant = currentTop } } |
Inside:
- UIVisualEffectView: object implements some complex effects.
- UIViewPropertyAnimator: object allows creating effects on the change of views.
- fractionComplete: The percentage of completion of the animation will change based on the offset value.
And this is the result:
References:
https://www.youtube.com/watch?v=R5CC1_QhrvY&list=PL0dzCUj1L5JGr7DuK-FxyIoabvP2ge5uY