Change Control Position at Runtime iOS

I have code that can change the size of a control at runtime.
But I have a stacked arrangement of ‘stuff’ in a containercontrol, and would like to have the option to re-arrange it if the user chooses

eg the container has controls arranged

A
B
C

and the user might want
A
C
B

I cannot set .Top at runtime.
I can GetBounds() , but cannot SetBounds()

How do we move stuff about in an Auto-Layout system?

You change the top constraint. In the auto layout editor, give the top constraint a name and then at runtime you can change its offset.

If you’re creating them at runtime, just keep an instance around so you can change them later.

3 Likes

Ive been reading the docs.
I found the bit about named constraints, and named the top constraints of two controls.

A control, however, doesnt have a ‘constraint’ property.

So I discovered that I can get the iOSLayout constraint by asking for a named constraint of the containercontrol. (that messes with my head, but hey ho…)

That gets me a valid constraint object - I can see the properties in the debugger.

But I cannot change the SecondItem property
eg I have a label at the top of the list, and would want to say

control.top (is relative to thelabel.bottom + 2)

constraint1.SecondItem = Thelabel

will not compile.
Nor does casting ThelblNav as an Object

As Greg said, the only thing you can change in a constraint is the Offset. The name of the constraint determines the constraint and what object you’re adjusting.

Var c As iOSLayoutConstraint
c = Self.Constraint("SystemLeft")
c.Offset = spacing
c = Self.Constraint("StarLeft")
c.Offset = spacing
c = Self.Constraint("PlanetLeft")
c.Offset = spacing

Constraints are always owned by the screen/container that the controls reside on because how the controls are laid out is controlled by the screen/container.

You don’t. If you want to change the secondItem, you’ll need to delete that constraint and create a brand new one.

1 Like

So, if I have to create a new one, the docs suggest something like this:

Var right As New iOSLayoutConstraint(RightConstraintField, _
  iOSLayoutConstraint.AttributeTypes.Right, _
  iOSLayoutConstraint.RelationTypes.Equal, _
  Self, iOSLayoutConstraint.AttributeTypes.Right, _
  1.0, 50, 1000)

And I guess I could then apply this new RIGHT constraint to a control.
However, it has no name property.
So somehow, I delete the named iOSLayout constraint, then add this unnamed one, after which I presumably can no longer access it by name?

I’m feeling thick… this is really obtuse…

Keep an instance of it in a property then update its Offset as needed or if you need to completely alter the bounds behavior then use RemoveConstraint to remove it, create a new one, then use AddConstraint to add that new version.

One key thing to watch out for is the Priority of the default constraints defined in the IDE, if you don’t remove them. When they’re set to “Highest” and you try to add a new “Highest” (1000) then the old one still takes precedence.

In addition to what Anthony wrote, Named constraints are only for design time constraints.

When you create a constraint in code, you’ll need to keep a reference to it somewhere if ever you need to disable it or change its offset.

2 Likes

They’re not fun to work with, and your app will crash when you do something wrong most of the time. I have a private issue showing this and asking for Xojo to catch them and raise exceptions instead.

1 Like

IIRC Apple doesn’t raise an exception either.

Looks to me like they do, but Xojo may be using an older API. I suppose some instances of creating invalid constraints may not throw exceptions, but we can thank opaque documentation for that question.

You can freely create invalid constraints. These constraints then throw an exception at runtime.

Source

As a side note, everything I’ve seen on SO refers to invalid layout constraints throwing exceptions rather than crashing as well.

And here’s the stack trace from one I reported:

Last Exception Backtrace:
0   CoreFoundation                	       0x18048d89c __exceptionPreprocess + 160
1   libobjc.A.dylib               	       0x18008409c objc_exception_throw + 56
2   CoreAutoLayout                	       0x1ca3c4430 -[NSLayoutConstraint setActive:] + 0
3   My iOS App.debug              	       0x104fbea78 iOSLayoutConstraint.Active.Set%%o<iOSLayoutConstraint>i4b + 20

That may be but Apple discourages “just handling them”. Their guidance is to fix the issues before your app gets to production. Now, maybe Xojo could figure out a way to only throw an exception in debug mode, but from what I’ve read, Apple may reject your app if you try to do that in production.

TBH, I don’t know what you’re expecting from an exception handler anyway. When the problem occurs, AutoLayout can’t figure out how to lay out the controls because it is Ambiguous (too few constraints) or Conflicted (too many). Maybe an untrappable exception? They’re typically called assertions…

I talked a little about this in my auto layout talk in Summer 2023, Because Xojo tried to simplify constraints for its developers, it glazes over two very important parts of constraints:

  1. Priorities - by default, Xojo constraints are all “required” which means if you have too many or too few, you get a crash.

  2. Hugging & Compression - not represented at all in xojo’s constraint system, but they control the priority of when a control is allowed to get larger or forced to get smaller when dealing with inequality constraints.

I also find that most users only use “equal to” for their constraints when the correct thing is “greater or equal” or “less or equal”. I find it usually affects people when they only consider a single device for layout purposes, thinking that the layout will just work.

Consider this scenario:

  1. Design Screen is 600pt wide
  2. Five canvases side by side, each 100pt wide
  3. Standard gap on left and right 20pt
  4. Dev decides on 15pt gaps between the canvases to make everything even.

100 x 5 = 500
500 + 20 + 20 = 540
540 + 15 x 4 = 600

Ok, so you’ve got a layout that works, ship it! But hold on a sec, there are 50 or so iPhone devices out there that can use your app, with 30 or so layout sizes. What happens if the screen width is 650, or worse 550? The constraints we made above make those layouts impossible if all of the constraints are required.

TLDR; if you’re going to use required “equals” constraints for everything, you should never have more than two per control, per direction:

  1. Left & Width or Top & Height
  2. Width & Right or Height & Bottom
  3. Right & Left or Top & Bottom

If you’re willing to use priorities, you can have more but you should not include any “required” constraints in that direction. You can also use inequalities as long as they don’t conflict. For instance don’t make width >= 30 and width <= 20 at the same time.

If you want better control over your constraints, check out my constraint project on GitHub.

1 Like

My preference would be for, in debug, the exception to be raised to a level where users can see what’s causing the assertion. With a meaningful message. At runtime, sure, crash and have it come back from the reviewer if it wasn’t caught in on-device or simulator testing. Point is: currently the framework just bails with a crash and a log that may or may not be helpful depending on the complexity of the view.

1 Like

Xojo may be able to trap the constraint log and somehow figure out what constraint cause the issue, but I’m still not convinced that an exception is the way to go. There are too many Xojo users who just use App.UnhandledException, an exception statement at the end of a code block or just wrapping code in a try-catch and to ignore the fact that something potentially really bad just happened.

Personally I’d go for an assertion that gave you as much detail as it can, the error message, the layout, the control and the constraint of its available (sometimes it’s not for under-constrained layouts), but then still have the app just bail.

Perfectly reasonable, and expected. If the view has an issue that causes it to render incorrectly and is unrecoverable, then it should be reported back to the debugger and execution terminated. Ideally highlighting the line causing the issue (usually on the Active = True line, I’d bet).

For constraints created in code, sure. Xojo wouldn’t be able to tell the difference, so it could still happen in a location the debugger can’t show.

Agreed. In any case, the process could be improved.