2

In a XIB file I have a CustomView related to a class with the same name.

The view content is quite simple.

There are a vertical stackview and inside the stackview there are two labels. The stackview is vertically aligned to the view that contains it:

enter image description here

The first label (title) contains a static text (always a few characters). The second label (subtitle) can have variable length text.

enter image description here

I add this CustomView with other similar views as a subview of a Content View, as it was a “row”.

for i in 0...aViews.count {

   var v = CustomView(frame: CGRect(x: 0, y: _y, width: 320, height: 100))
   v.labelText = "my long label text ..."
   contentView.addSubview(v)
   // ...
}

As you can imagine, the title label should have a fixed position (top and left), that is, there cannot be a row with the title starting at 10 points and another at 14.

enter image description here

I must admit I naively thought the position of the label would have been automatically managed by the same fact that I aligned vertically the stackview. I was wrong and I noticed no problem at all, until they told me the second label, the subtitle, could contain more than one line.

I added lines to the label directly in the storyboard, and found out that:

  1. the container of the stackview doesn’t change its height based on the height of its content;

  2. the position of the “fixed” elements is maybe vertically centered but not fixed;

enter image description here

What I need is the following:

  • the labels should be “grouped” and aligned vertically: there should be the same amount of space from the top of the first label to the upper border of the container and from the bottom of the second label to the bottom border;
  • when the second label has to display more than one line, the container view should change its height accordingly;

Is this possible? How should I design my view?

3000
  • 187
  • 1
  • 12
  • What this `contentView.addSubview(v)` for?? What it do? – dahiya_boy Feb 22 '19 at 09:31
  • Your `contentView` and `CustomView` doesnt have any constraints. – Cerlin Feb 22 '19 at 09:36
  • @dahiya_boy: as I explained, I add many similar views to a contentView (imagine it like a tableview, but with different heights for any cell and different parameters for title and subtitle) – 3000 Feb 22 '19 at 10:23
  • @CerlinBoss: the single customview is currently positioned using frames. What kind of constraint are you thinking of? – 3000 Feb 22 '19 at 10:26
  • @3000 If you are adding views in `stackView` with dynamic loop on run time, then better you use tableview. It is more much handy. If there any specific requirement you wanted to do then post a new question. – dahiya_boy Feb 22 '19 at 10:33
  • @dahiya_boy: I'm NOT adding views in a stackview in a loop at runtime. I'm adding custom views to the content view of a scrollview. – 3000 Feb 22 '19 at 10:34
  • Following best practices you should use table-view for this. But if you want to do it with current implementation then 1st, I think you should not assign frame to `CustomView`, just initialize the object. 2nd assign number of lines of label to 0 so that it can automatically calculate according to content and 3rd assign top and bottom constraint rather than vertically align to that stack-view can provide the heigh to view. – Ankit Jayaswal Feb 22 '19 at 10:35
  • 1
    @3000 It is very bad practise. Developer avoid scrollview and use tableview for this conditions. Later it will be headache to manage scrollview and its content. – dahiya_boy Feb 22 '19 at 10:36
  • I think if you fix a frame for your CustomView, it will stick to that frame and wont grow as per content. Set frame as CGRect.zero and then add constraints programatically for it to update its height based on content – Cerlin Feb 22 '19 at 10:36
  • @dahiya_boy: it's a very common scrollview scenario, don't worry – 3000 Feb 22 '19 at 10:37
  • @3000 For now, You wanted to add 2 labels in stackview vertically and 2nd label label is multi-line. You need to manage the stackview height according to the inner labels height. Is that your point?? P.s. Is your stackView is inside ScrollView ?? – dahiya_boy Feb 22 '19 at 10:39
  • @dahiya_boy: no, the stackview is inside the CustomView, that's one of the views I add to the Content View of a scrollview. The problem here is NOT the scrollview, I neither talk about it in my question – 3000 Feb 22 '19 at 10:41
  • @AnkitJayaswal: even if you can't see it from the images I posted, my views can be very different: tableviews are done to show repeated "cells" of data: I have no cells, I have views that can be completely different from each other – 3000 Feb 22 '19 at 10:45
  • In that case you can load different type of cell in same table view. Just check which table cell you need to load in `cellForRowAtIndexPath`. – Ankit Jayaswal Feb 22 '19 at 10:47
  • @AnkitJayaswal: I know the tableview, I use it all the time but this is not the case – 3000 Feb 22 '19 at 10:48
  • In that case, you can go with your current implementation too. Share the code on GitHub if you still not able to crack it. – Ankit Jayaswal Feb 22 '19 at 10:49
  • @3000 - Is this your goal? https://imgur.com/a/FdK6Tip Orange is root view, cyan is a "content view", each "line" is an instance of a Custom View from a xib. Everything is auto-sizing based on the amount of text. – DonMag Feb 22 '19 at 14:53
  • @DonMag: this is similar but not the same, that is, in the real life I can have views very different, eg. with no title or subtitle, with/without buttons, images views, etc. But, yes, of course the auto-sizing part of your example is exactly what I want (in the meantime I refactored my local sample to use a stackview, I don't have a GitHub account but tomorrow I can provide the project as a downloadable zip, for anyone interested) – 3000 Feb 22 '19 at 15:20
  • @3000 - You haven't posted much here on Stack Overflow, but a tip... try to post your actual question, not *"here's my question, but it's not really what I need to do."* If your CustomView is not sizing itself based on its content (the labels in the stack view) then you have something wrong with your constraints, or you're loading / setting it up incorrectly. I can give you an example project if you're a bit more clear on what exactly you need to figure out. – DonMag Feb 22 '19 at 15:43
  • @DonMag: it's true, I started posting some days ago and I thought my question was clear. I have explained what I've tried, then I asked (final lines) the behavior I needed from this part of this project. Maybe I wasn't able to explain better my needs, sorry. I simply have a scrollview, in the scrollview there's a contentview. I need to fill that contentview with views that can have any height (and content). When I posted this question, I was creating views of the same height just to test if by adding more text the view would change its height. – 3000 Feb 22 '19 at 16:04

2 Answers2

1

I think you are mixing absolute positioning of outer views (i.e. manually setting frames) and using autolayout constraints for their inner components, and wrongly expect the dynamic autolayout part to somehow "reach all the way up" to the outer views. For autolayout to do that, you'll need to use autolayout all the way up, including the way CustomViews are positioned in your contentView.

If for whatever reason you do not want to use a TableView or CollectionView for this you could also, for example, try adding your CustomViews as arranged subviews to a vertical stackview that has top/bottom/leading/trailing constraints to contentView, then replace the "Align Center Y" constraint of your CustomView with "Align Top" and "Align Bottom" constraints to actually allow your labels to "push" from the inside if they need more vertical space.

Edit here's a quick sketch to illustrate that setup: enter image description here

Edit 2 here are a couple of screenshots to clarify further. With a basic layout like this: enter image description here

the result will look like this at runtime: enter image description here

Note that the UIStackViews use the "Equal Spacing" Distribution in this example. If you want to create and add your CustomViews programmatically, use the StackView's func addArrangedSubview(_ view: UIView).

Good luck!

Gamma
  • 1,347
  • 11
  • 18
  • As for your first paragraph, yes and no, that is: I thought I could do somethig like this: https://stackoverflow.com/questions/39526929/set-uiview-size-to-fit-subviews AND get the "new" height of the view to use it in the design with frames. Your sketch is very cool, but I don't understand why do I need a stackview (I currently use a simple view. If I have to add subviews with autolayout, couldn't I add constraints to the subviews?) – 3000 Feb 22 '19 at 11:09
  • You can absolutely add the constraints to your subviews yourself, no problem. Using a "StackView" instead simply saves you some time from having to manually manage the constraints (if all you want is to "stack" the subviews anyway). – Gamma Feb 22 '19 at 11:17
  • The view we're talking about is a bit more complex: in my question, I had to remove a lot of "noise" to avoid people getting lost with unimportant details. For example: at the top of the contentView there's an imageview directly created into the storyboard, with complex autolayout constraints. So, the stackview should be put below this view and pinned to its bottom. I can try it next monday, thanks – 3000 Feb 22 '19 at 12:52
  • I made a little attempt, but it doesn't work: when you add arrangedSubviews to a stackview, you must provide constraints like this one: `aView.heightAnchor.constraint(equalToConstant: 100)`. Now, even if you add a lot of text (besides the fact that this text seems to flow in every direction), the text in the label can't resize the view upon view instantiation: the view apparently stays the same height – 3000 Feb 22 '19 at 14:23
  • @3000 see 2nd edit, maybe it helps to make the example clearer – Gamma Feb 22 '19 at 15:28
  • 1
    Regarding the `equalToConstant: 100` part: don't give the view an explicit static height constraint! This is exactly what we do not want. What we want is the system to dynamically figure out the height at runtime. So instead, just give it enough constraints that the layout engine can figure out how much space each component actually needs. Refer to the example in my answer for one way to achieve this. – Gamma Feb 22 '19 at 15:44
1

OK - looking at your project, you're not far off... just a couple key items.

  1. In your CustomView.xib, you had no constraint between Fixed Label and Date Label. The result is that the Date Label grows up from the bottom of the view, with nothing to stop it.

  2. Also in your CustomView.xib, you had the Fixed Label constrained to the trailing edge... I assume you want it left-aligned with Date Label

  3. When creating your CustomViews, you were setting a height constraint of 100 -- which defeats the purpose of allowing the content to determine the size.

  4. In CustomView class, you had contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight] commented out. That line should be there.

  5. In your view controller, you added a height constraint to contentView with a low priority. Best option is to make that a Placeholder constraint (double-click the constraint, and select the Placeholder checkbox). That way the constraint is removed at run-time, but is there during design (and Storyboard doesn't complain).

  6. You were doing a few other un-necessary things - likely you were trying different stuff to get it to work.

  7. The view setup (adding your views) is best done in viewDidLoad() -- definitely not in viewDidLayoutSubviews()

  8. I'm guessing that, in your CustomView, instead of an explicit width of 200 you probably want to constrain the Date Label leading and trailing, allowing it to horizontally stretch based on the device width... but I left it at 200.

If you can follow that information, you should be able to fix the issue(s). But, I put the project up as a GitHub repo for you, so you can get it in a "working" state, and so you can see the changes.

Here's the link to the repo: https://github.com/DonMag/ATester2

Here is the result:

enter image description here

And scrolled up a bit:

enter image description here

DonMag
  • 69,424
  • 5
  • 50
  • 86
  • First of all THANKS, I still haven't had the chance to download your project but the images clearly shows the result I was searching for. I try to answer one of your points: (3) I set it because I erroneously thought something was necessary to give the view frame a starting height: in my tests, I found that if I removed that line, the "graphic" result was 100 (well... maybe!), but the output print for the view height was 25 (maybe the intrinsic content size of the label?) – 3000 Feb 23 '19 at 07:32
  • I tried it, it's exactly what I was trying to do. :-) I've seen you removed a lot of stuff. It's true (point 6) I was trying things, but I was quite sure I needed the constraints of the scrollview (eg. `contentLayoutGuide` and `heightAnchor`), your version is much more simpler (and clean, of course) – 3000 Feb 23 '19 at 07:50