bmt22033
bmt22033

Reputation: 7240

Animate the resizing of full screen UIView to show another UIView at bottom of screen

I'm updating an iOS Swift app (that someone else created) which contains a view ("View A") which initially fills the entire screen. For what it's worth, this view is a map. When a user taps on a point of interest on the map, the height of the map view ("View A") shrinks to 75% of the height of the screen and another view ("View B") is shown in the bottom 25% of the screen. Here's a visual depiction of that scenario.

enter image description here

Currently, this is being accomplished like this:

// this is "View A"
var mapView: MGLMapView!
// this is "View B"
var pageViewController : UIPageViewController!
var pageViewControllerFrame = CGRect()

override func viewDidLoad() {
    super.viewDidLoad()

    // setup map view
    mapView = MGLMapView(frame: view.bounds, styleURL: styleUrl)
    mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    view.addSubview(mapView)

    // setup UIPageViewController
    pageViewControllerFrame = CGRect(x: 0, y: self.view.bounds.height * 0.75, width: mapView.bounds.width, height: self.view.bounds.height / 4)

    pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
    pageViewController.view.frame = pageViewControllerFrame
    pageViewController.view.autoresizingMask = [.flexibleWidth]
    pageViewController.view.translatesAutoresizingMaskIntoConstraints = true
    view.addSubview(pageViewController.view)
    pageViewController.view.isHidden = true
}

@objc func handleMapTap(sender: UITapGestureRecognizer) {

    if userTappedOnPoi == true {
        self.mapView.frame = CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height * 3/4)
        pageViewController.view.isHidden = false
    } else {
        // user tapped on the map but not on a POI so hide pageViewController and reset map frame to take up the entire screen
        pageViewController.view.isHidden = true
        mapView.frame = view.bounds
    }
}

As you can see, there's a tap gesture recognizer on the map view. If the user taps on a POI, then the map view's frame will be adjusted so the height is 75% of the view's height and then the bottom view ("View B") is made visible. This is working but I think it would look better if it were animated so as the height of the map view is shrinking, the view at the bottom is sliding up onto the screen.

I've been playing with animate(withDuration:delay:options:animations:completion:) but so far haven't had any luck getting the animation to work properly. Most of the things I've tried (even with a duration of something like 3.0 seconds) still seem to result in the map view changing it's height immediately. I'm guessing this might be because there are no auto layout constraints in place to animate?

Upvotes: 0

Views: 905

Answers (2)

Shehata Gamal
Shehata Gamal

Reputation: 100503

Using auto-layout is better in your case as with the pager pageViewController.view.isHidden = true/false is hidden/shown instantly and with no sync to the collapse of the map , so set constraints like

Mapview : top , leading , trailing and multiplier height

pager : top to map , leading , trailing and bottom to view

Then animate the change of the map's height multiplier like

var heightCon:NSLayoutConstraint!

heightCon.constant = self.view.bounds.height * 0.75
UIView.animate(withDuration: 0.5) {
  self.view.layoutIfNeeded()
}

Upvotes: 1

Fabio Felici
Fabio Felici

Reputation: 2906

You can also use UIStackView so you can easily get animations by changing isHidden property.

Example:

import UIKit
import PlaygroundSupport

class VC: UIViewController {

    private let view1 = UIView()
    private let view2 = UIView()

    override func viewDidLoad() {
        super.viewDidLoad()
        view1.backgroundColor = .red
        view2.backgroundColor = .green
        view2.isHidden = true
        let stackView = UIStackView(arrangedSubviews: [view1, view2])
        stackView.translatesAutoresizingMaskIntoConstraints = false
        stackView.axis = .vertical
        view.addSubview(stackView)
        NSLayoutConstraint.activate([
            stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            stackView.topAnchor.constraint(equalTo: view.topAnchor),
            stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            view2.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.25)
        ])
        view1.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleMapTap(sender:))))
    }

    @objc func handleMapTap(sender: Any) {
        UIView.animate(withDuration: 0.3) {
            self.view2.isHidden = !self.view2.isHidden
            self.view.layoutIfNeeded()
        }
    }
}

PlaygroundPage.current.needsIndefiniteExecution = true
PlaygroundPage.current.liveView = VC()

Upvotes: 1

Related Questions