1

I currently have this interface whereby I am putting a MapView and a UIView on top of each other. The UIView is where I put my chart overlay (eg: Heart Rate).

This is fine when the device is in portrait mode. However, I would like to explore a different layout when in landscape mode whereby the UIView(HR Chart) and the MapView is side by side.

Is this doable in Interface Builder? Or only in code? (I would like to only use UIKit to better support lower OSes)

note : I tried in IB but can't seem to figure out how to get the Stack such that the MapView and UIView is on top of each other in Portrait mode.

enter image description here

This is how it looks like now. But I would like to the Chart and the Map side by side in landscape mode. enter image description here

Edit for clarification on current layout in portrait mode and desired outcome in landscape mode. How the layout looks like. enter image description here

app4g
  • 670
  • 4
  • 24

1 Answers1

2

This can be done by using Trait Variations on stack views, changing the Axis between Vertical and Horizontal.

Consider this layout - I've given the labels background colors to help visualize:

enter image description here

  • Each "name / Value" label set is in a Vertical Stack View
  • Each "Pair" of "sets" - Power / Slope, Distance / Elapsed Time, Heart Rate / Cadence - is also in their own Vertical Stack View
  • The 3 "pairs" are in a Horizontal Stack View

Select the RedPairStack and, in the Attributes Inspector pane, click the + icon to the left of the Axis:

enter image description here

From that popup, change Width to Any and change Height to Compact and click Add Variation:

enter image description here

For the new hC variation, select Horizontal:

enter image description here

Do the same for the GreenPairStack and the BluePairStack.

Now, when the device is has a Regular Height, those three stack views will use a Vertical Axis... in Compact Height, they'll use a Horizontal Axis.

Here's the result:

enter image description here

enter image description here

Here is the source to the Storyboard so you can inspect everything: https://pastebin.com/dqNp8CcC


Edit - after comments...

To get side-by-side, we need a few changes:

enter image description here

Looks very similar, but:

  • embedded the "label/value" horizontal stack in a UIView - "PairsContainerView" (cantaloupe background)
  • embed that view plus the Map view in a Vertical stack view

I removed the Trait variations from the PairStacks, then constrained the HorizontalStack to all 4 sides of the "PairsContainerView"

The result looks good at first, but when rotating the labels get stretched out. So, we add a couple new Trait Variations.

First, set the Content Hugging Priority on all the labels to Required.

Next, for Compact Height (hC) trait, we add a CenterY constraint to the HorizontalStack, and remove the Top and Bottom constraints.

Here's the new results:

enter image description here

enter image description here

and with colors removed:

enter image description here

enter image description here

Since you didn't provide an image of how you want your layout to look, this may be close enough to get you on your way.

New Storyboard souce: https://pastebin.com/TkVbNUVE


Edit 2

Start with the first example above...

Add a UIView as a sibling of the Map View and assign its Custom Class to HeartRateView:

enter image description here

Here's my quick HeartRateView (red squiggly line):

class HeartRateView: UIView {

    let shapeLayer = CAShapeLayer()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    
    func commonInit() -> Void {
        layer.addSublayer(shapeLayer)
        shapeLayer.strokeColor = UIColor.red.cgColor
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.lineWidth = 1.0

        let pth = UIBezierPath()
        let topY: CGFloat = 40.0
        var pt: CGPoint = CGPoint(x: 0.0, y: topY)
        pth.move(to: pt)
        for _ in 1...30 {
            pt.x += 2
            pt.y = topY + CGFloat.random(in: 0...40)
            pth.addLine(to: pt)
        }
        
        shapeLayer.path = pth.cgPath
    }

}

Add these constraints to HeartRateView:

  • Top equal to MapView Top
  • Leading equal to MapView Leading
  • Width equal to MapView Width
  • Height equal to MapView Height

As you would expect, this will "overlay" the HeartRateView on the MapView.

Now, in Storyboard, switch the Orientation to Landscape and click the "Vary for Traits" button. From that popup, select Height

Select the MapView in the Document Outline pane, and over in the Size Inspector pane, select the Trailing to Safe Area and Leading to Heart Rate View constraints:

enter image description here

Tap Delete on your keyboard - it will delete those constraints only for this Trait variation. Should look like this:

enter image description here

and your view should look like this:

enter image description here

Select the Heart Rate View and constrain its Trailing to the Safe Area (I'm using 4-pts padding on everything). Your view should now look like this (Heart Rate View has a clear background, so we can only see the selection handles):

enter image description here

Now add a constraint from MapView Trailing to HeartRateView Leading (of 4 for the little padding):

enter image description here

Because HeartRateView is still constrained Equal Width to MapView, they automatically fill out the width.

Last step, click Done Varying

Now we get this output:

enter image description here

enter image description here

Here's the third Storyboard source: https://pastebin.com/2hPXisAH

DonMag
  • 69,424
  • 5
  • 50
  • 86
  • The OP wants to achieve a side-by-side layout in the landscape, so I think the map should be on the right side and the dashboard on the left side of the screen. – Evgeny Karkan Jan 07 '21 at 15:07
  • @donMag first of all. Very Impressive. It took me a few days to get the Power/Slope etc into 1 line in landscape mode) correct. (and my hatred of IB grows :-p). 2ndly, I am very sorry that (IF) you spent so much time on this but I actually wanted the "mapView" and the "UIView" to be side by side in landscape mode. (perhaps and highly likely my description wasn't all that great). In Portrait mode, the MapView and UIView(with the HR Chart) is on top of each other. In Landscape mode, I would like to explore them being side by side.. (note: I've not looked at your source code) – app4g Jan 07 '21 at 15:08
  • @myjunk - ah, thought you were showing your desired layout. See the edit to my answer. – DonMag Jan 07 '21 at 16:04
  • @DonMag - I have added a picture of what the view looks like when I described that the Heart rate chart (Uiview) is on top of the MapView (like a zstack). The UIView is transparent to draw the user’s heart Rate Data (it’s those squiggly Red line on the screenshot). It the same exact size as the mapview. I’ve added a side view to show the current layout. sorry for the confusion. – app4g Jan 07 '21 at 20:27
  • @myjunk - I added **Edit 2** ... if you've never used `Vary for Traits` it may be a little confusing, although I think I clarified everything. If you have trouble following, search for `autolayout trait variation tutorials` and check out a few of them. – DonMag Jan 07 '21 at 22:24
  • @DonMag I really _really_ appreciate this. This is so wonderful. I just tried it (although It took me like 7-8 tries and a few hours) but I got the side-by-side that I wanted. Not only that, even the first part of your solution (the 3 V-Stack embedded into the 1 H-Stack) blew me away. I was trying so hard to figure out how to get the 6 boxes so nice and "Square" before this. Only thing, the Time gets truncated when > 1 hour. https://imgur.com/a/CDd2DiP which can be rectified using "fill proportionally" on the H-Stack (still need to test as I screwed up something) – app4g Jan 08 '21 at 10:52
  • @DonMag In some situations, (eg: the user is selecting a NON-Map exercise) the map is hidden. (I am doing the hiding in code using ```MapView.isHidden = true``` and in this case, the resultant looks like --> https://imgur.com/a/wp8q4GF. I have tried to edit it such that the ChartView is ```greater or equal to``` 5 & remove made width constraint from ```equal to``` to ```greater or equal to``` and the like. But can't get it to expand fully when the view is hidden. Could you help out with that (here or do I need to ask a new question?) I'm all ready to upvote this and "select as answer" anyway – app4g Jan 08 '21 at 11:01
  • *"the Time gets truncated when > 1 hour"* ... generally, a layout is designed with the widest anticipated strings in mind so you don't run into this issue. `Fill Proportionally` is almost certainly not going to give you the desired result (I recommend forgetting that setting entirely - it's probably the most misunderstood aspect of stack views). What may give you acceptable results: HorizontalStack to `Fill` and constrain `BluePairStack` equal width to `RedPairStack` – DonMag Jan 08 '21 at 13:28
  • You probably also want to give `RedStackView` a Proportional Width constraint ... Constrain it Equal Width to `HorizontalStack` with a **Multiplier** of `0.25`. Should give you acceptable results. `MapView.isHidden = true` ... does not remove its constraint relationships to other views. Couple ways to approach, but... – DonMag Jan 08 '21 at 14:21
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/227038/discussion-between-donmag-and-myjunk). – DonMag Jan 08 '21 at 14:21
  • 1
    @myjunk - if you're still trying to get this layout working... the general idea is to create 2 leading constraints for the HeartRateView: one for Map visible and one for Map hidden. Set the "MapVisible" constraint to a higher Priority when the Map is visible, and set the "MapHidden" constraint to a higher Priority when the Map is hidden. I put an updated Storyboard and associated View Controller code here: https://gist.github.com/DonMag/1ddc83e04584756ad750855b16a14ba1 – DonMag Jan 10 '21 at 15:09
  • @DonMag wow..wow..wow.. just tried it and it's blows my mind. I'm still figuring out how it works and what not but it's really is . I will need to retool it to programatically change the layout based on orientation but this is way way better then me being stucked. KUDOS!!!! – app4g Jan 12 '21 at 09:13
  • @DonMag I did not know that one could assign an IBOutlet to a constraint. wow.. – app4g Jan 12 '21 at 09:32