2

This seems like it should be easier that it currently is being.

I am trying to center a UIView with an aspect ratio of 1:1 (A square) in any iOS device it is drawn in, regardless of orientation.

For detail: My view has a draw that updates on a timeInterval. I was using a full screen view and computing my square on each draw. On orientation change the whole view went to hell. I assumed that if the view was square, I could trust the orientation change animation.

My constraints have been failing repeatedly, which is why this seems like it should be easier:

View (Square)
    Constraints
        aspect 1:1
Constraints
    View.centerX = centerX
    View.centerY = centerY
    View.leading ≥ leadingMargin + 5 @ 800
    View.top ≥ Top Layout Guide.bottom + 5 @ 800
    trailingMargin ≥ View.trailing + 5 @ 800
    Bottom Layout Guide.top ≥ View.bottom + 5 @ 800

I have the Content Hugging Prioity at 250
I have the content Compression Resistance at 750

This leaves the constraint errors:
- Missing Constraint: Need constrains for: X position or width
- Missing Constraint: Need constrains for: Y position or height

My confusion is that I can't lock into one dimension because on rotation I need to lock into the other.

As mentioned... this seems like it should be easier. Center a Square UIView with a border of 5 at the thinner dimension.
(5 to the sides in portrait, 5 to the top and bottom in landscape)

Suggestions warmly appreciated, Explanations would be beyond helpful.

enter image description here

Dru Freeman
  • 1,766
  • 3
  • 19
  • 41
  • 1
    You want the square to have the maximum size available while still being square? And then center it? – luk2302 Oct 16 '15 at 19:33
  • Do you have a working solution and looking for a simpler version or are you asking for any working solution at all? – luk2302 Oct 16 '15 at 19:39
  • The solution I have doesn't work as the view vanishes when I rotate and IB complains about the missing constraints – Dru Freeman Oct 16 '15 at 19:44
  • Okay, I am experimenting around with an IB-only solution which is probably what you would like most!? It is most definitely not a trivial task – luk2302 Oct 16 '15 at 19:45
  • I've discovered the lack of triviality. – Dru Freeman Oct 16 '15 at 19:47
  • what might help you is probably http://stackoverflow.com/questions/25766747/emulating-aspect-fit-behaviour-using-autolayout-constraints-in-xcode-6 !? Will try to get something working... – luk2302 Oct 16 '15 at 19:50
  • For an iPhone app you can do this with size classes, turning on and off the top/side constraints as appropriate. Unfortunately (and I don't know why Apple did it is way) you can't tell the difference between iPad portrait and landscape with size classes alone. So for a universal app you will need to have some code in the rotation method that checks the width/height and determines portrait/landscape (you only need to know if width>height) and turns on/off constraints – Paulw11 Oct 16 '15 at 19:53
  • @Paulw11 That iPad change is what caused me to give up on the size classes – Dru Freeman Oct 16 '15 at 19:54
  • I give up :/ would be happy to see a IB-only answer given at some point ;) – luk2302 Oct 16 '15 at 20:10

2 Answers2

1

Here is an approach that I just put together. I couldn't do it entirely in IB, but at least the run-time code is limited to activating/deactivating constraints rather than having to add/remove or compute/change any sizes.

In my storyboard I have an inner UIView with the following constraints

  • Center X
  • Center Y
  • Leading space to superview = 5, priority = 999
  • Trailing space to superview = 5, priority = 999
  • Top space to superview=5, priority=1000
  • Bottom space to superview=5, priority=1000

I created IBOutlets for the last four constraints and this is my view controller -

class ViewController: UIViewController {

    @IBOutlet var leadingConstraint: NSLayoutConstraint!
    @IBOutlet var trailingConstraint: NSLayoutConstraint!
    @IBOutlet var topConstraint: NSLayoutConstraint!
    @IBOutlet var bottomConstraint: NSLayoutConstraint!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        self.setConstraintsForSize(self.view.frame.size)
    } 

    override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
        self.setConstraintsForSize(size)
    }

    func setConstraintsForSize(size:CGSize) {
        if (size.width > size.height) {
            self.leadingConstraint.active=false;
            self.trailingConstraint.active=false;
            self.topConstraint.active=true;
            self.bottomConstraint.active=true;
        } else {
            self.leadingConstraint.active=true;
            self.trailingConstraint.active=true;
            self.topConstraint.active=false;
            self.bottomConstraint.active=false;
        }
    }  
}

This works on iPhone and iPad, including iPad in windowed/splitscreen mode

Paulw11
  • 108,386
  • 14
  • 159
  • 186
0

The square needs to specify its size.

Square centerX = Parent centerX
Square centerY = Parent centerY

At this point, the view is centered, but there's nothing to let autolayout determine the size of the view.

Square width = 200
Square aspect = 1

If you want the square to be a portion of the parent's width, that's straightforward as well.

Square width = Parent width * 0.5 //or any other multiplier

You'll need to adjust the width constraint depending on how you want the square to look in landscape.

To have "the largest square possible", you can programmatically set the width constraint to min(parentWidth, parentHeight) and adjust on orientation.

Alternatively, leading, trailing, centerX, centerY, and aspect should get you similar results. Any set of constraints needs to be able to logically determine position and size without ambiguity.

Update:
In IB:

 Square centerX = Parent centerX
 Square centerY = Parent centerY
 Square aspect = 1

In code:

 if parent width > parent height
 Square width = parent height - 2 * margin

 else
 Square width = parent width - 2 * margin

Adjust this on rotation

Stefan Kendall
  • 66,414
  • 68
  • 253
  • 406
  • Stefan, can you clarify if this is done in IB constraints or in code. I've edited the above for clarity. – Dru Freeman Oct 16 '15 at 19:42
  • @LordAndrei I updated my answer. The most obvious solution to me is to put the easy and fixed constraints in IB and add a constraint in code for the thing that can change/is tricky, the square's width. There are likely many other ways to do this, but this seems most obvious. – Stefan Kendall Oct 16 '15 at 19:51
  • 1
    One thing to note is that IB will show errors for that view, because there's no width/height defined. That's fine; you're going to add the missing constraint programmatically. Or you could add the constraint for portrait in IB and adjust it on rotation to remove the errors when editing the xib/storyboard. – Stefan Kendall Oct 16 '15 at 19:53
  • A placeholder constraint? – Dru Freeman Oct 16 '15 at 19:53
  • 1
    @LordAndrei No, not a placeholder constraint, but a constraint that you connect as outlet to your source code and change as you wish. – luk2302 Oct 16 '15 at 20:06
  • @luk2302 that's exactly what I meant. Thanks for clarifying. – Stefan Kendall Oct 16 '15 at 21:13