June 16

Recreating the Parallax Menu in Aeris Pulse iOS

We’ve had several users of our AerisWeather iOS SDK ask us about whether the parallax UITableViewCells in the map layer menu of Aeris Pulse was available as part of the SDK, or how we achieved the effect.

 

Although this menu is not offered as part of our iOS SDK, it’s really easy to implement. There are just a few main steps you need to perform to get this effect working in your own apps. The following code samples are provided using Swift, but can easily be ported over to Objective-C if you haven’t jumped into Swift yet.

  1. Generate background images with heights larger than the expected height of your UITableViewCells within your app. An image height twice that of your cells is usually sufficient. For example, if your menu cells are configured with a rowHeight of 100pt, generate background images with a height of at least 200px. Remember you can have your UIImageView scale this image to fill its bounds as needed.
  2. Add your UITableView instance and create a custom UITableViewCell subclass, such as
    MenuItemTableViewCell

     . Make sure to also register this custom cell class with your table view instance using

    registerClass:forCellIdentifier:

     so the right cell is dequeued by the table view.

    tableView = UITableView()
    tableView.dataSource = self
    tableView.delegate = self
    tableView.rowHeight = 100
    tableView.separatorColor = .whiteColor()
    tableView.registerClass(MenuItemTableViewCell.self, forCellReuseIdentifier: "MenuItemCell")
    view.addSubview(tableView)
  3. Create a 
    ParallaxBackgroundView

      class to be used as the background view of your custom table view cells. This view should just contain a single UIImageView as a subview and the necessary public method for receiving the necessary UIScrollView scrolling events to adjust the image view:

    import UIKit
    
    class ParallaxBackgroundView: UIView {
      var image: UIImage? {
        set(newValue) {
          imageView.image = newValue
        }
        get {
          return imageView.image
        }
      }
      
      private var imageView: UIImageView!
      private var imageTopConstraint: NSLayoutConstraint?
    
      override required init(frame: CGRect) {
        super.init(frame: frame)
        setup()
      }
      
      required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
      }
      
      private func setup() {
        clipsToBounds = true
        
        imageView = UIImageView()
        imageView.contentMode = .ScaleAspectFill
        imageView.translatesAutoresizingMaskIntoConstraints = false
        addSubview(imageView)
        
        // layout
        imageTopConstraint = imageView.topAnchor.constraintEqualToAnchor(topAnchor)
        
        addConstraint(imageTopConstraint!)
        addConstraint(imageView.leadingAnchor.constraintEqualToAnchor(leadingAnchor))
        addConstraint(imageView.trailingAnchor.constraintEqualToAnchor(trailingAnchor))
        addConstraint(imageView.heightAnchor.constraintEqualToConstant(200))
      }
      
      func updateForView(view: UIView, inScrollView scrollView: UIScrollView) {
        if let parentView = scrollView.superview {
          // Convert the cell's frame to the parentView's coordinate space so we can determine its distance from the parentView's center
          let rect = scrollView.convertRect(view.frame, toView: parentView)
          let distanceFromCenter = parentView.frame.height / 2 - rect.origin.y
          
          // Calculate the height difference between the image view and the view
          let diff = imageView.frame.height - frame.height
          
          // Calculate the amount to shift the vertical position of the image view based on the parentView's distance from center and height delta
          let shiftY = (distanceFromCenter / parentView.frame.height) * diff
          
          // update top constraint for image view width new offset
          imageTopConstraint?.constant = -(diff / 2) + shiftY
          setNeedsLayout()
        }
      }
    }
  4. Add an instance of this ParallaxBackgroundView to your custom UITableViewCell class, MenuItemTableViewCell, that will be used for the cells in your menu. Make sure this view is inserted at the bottom of the view hierarchy so all other cell content appears above it:
      private func setup() {
        translatesAutoresizingMaskIntoConstraints = false
        
        titleLabel = UILabel()
        titleLabel.font = UIFont.systemFontOfSize(18)
        titleLabel.textColor = .whiteColor()
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(titleLabel)
        
        parallaxView = ParallaxBackgroundView()
        parallaxView.backgroundColor = UIColor(white: 0.5, alpha: 1)
        parallaxView.translatesAutoresizingMaskIntoConstraints = false
        contentView.insertSubview(parallaxView, atIndex: 0)
        
        // layout
        let views = ["parallaxView": parallaxView, "contentView": contentView]
        let horizontalConstraints = NSLayoutConstraint.constraintsWithVisualFormat("H:|[parallaxView]|", options: .AlignAllTop, metrics: nil, views: views)
        contentView.addConstraints(horizontalConstraints)
        
        let verticalConstraints = NSLayoutConstraint.constraintsWithVisualFormat("V:|[parallaxView]|", options: .AlignAllLeft, metrics: nil, views: views)
        contentView.addConstraints(verticalConstraints)
        
        contentView.addConstraint(titleLabel.leadingAnchor.constraintEqualToAnchor(contentView.leadingAnchor, constant: 10))
        contentView.addConstraint(titleLabel.bottomAnchor.constraintEqualToAnchor(contentView.bottomAnchor, constant: -10))
      }
      
      func updateParallaxForScrollView(scrollView: UIScrollView) {
        parallaxView.updateForView(self, inScrollView: scrollView)
      }
  5. Implement the
    scrollViewDidScroll:

      method on your UITableView’s delegate, and call the

    updateParallaxForScrollView:

      method for all visible cells of your table view. This is the code responsible for updating the image view’s vertical position in response to the cell’s position relative to the scroll view’s center:

    extension ViewController: UITableViewDelegate {
      func scrollViewDidScroll(scrollView: UIScrollView) {
        for cell in tableView.visibleCells {
          if let cell = cell as? MenuItemTableViewCell {
            cell.updateParallaxForScrollView(scrollView)
          }
        }
      }
    }

And that’s it, you now have a working recreation of the Aeris Pulse map layer menu (aside from the actual images and other details)!


Have any questions or suggestions for future features with our AerisWeather iOS SDK? We’re constantly working to improve our libraries you use to implement and interact with our various weather services, so feel free to contact us with your input.

 

Share this post:

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.

AerisWeather
{{page_content}}