layout icon indicating copy to clipboard operation
layout copied to clipboard

Problem with UILabel text wrapping the view's intrinsic width in an expression.

Open Dragonspell99 opened this issue 5 years ago • 8 comments

I'm trying to create a UILabel that will size itself automatically according to the following rules:

  1. When the text length is short, the UILabel width should match the intrinsic content size. In other words, the label should grow or shrink to wrap the intrinsic content size.
  2. When the width of the label exceeds a certain threshold, it should stop growing in width and wrap to another line so that it never exceeds the width threshold.

In order to accomplish this, I have defined my view as follows:

<UIViewController>
    <UILabel
        backgroundColor="#CCC"
        left="24"
        top="24"
        width="min(auto, 320)"
        numberOfLines="0"
        text="This text is to verify line wrapping when using an expression that contains the auto keyword."
    />
</UIViewController>

As can be seen in the width expression, I would like the width to be the smaller of the auto width or 320. When I reduce the text to just a few words, this works as expected. However, when the text is too long, as in the supplied example, the label doesn't grow beyond 320 as desired but it doesn't wrap the text. It truncates it instead and remains at 1 line.

Conversely, if I change my width expression to something like: width="min(1000, 320)" Leaving everything else the same, the label wraps properly with a width at 320.

Therefore, there seems to be a problem with causing the label to wrap text when using the auto keyword in the width expression.

Dragonspell99 avatar Oct 17 '18 02:10 Dragonspell99

@Dragonspell99 it looks like a bug - I'll investigate.

In the meantime, it works if you use min(auto, 100%), so you can get the same effect by wrapping the label in a fixed-width container view:

<UIView
    left="24"
    top="24"
    width="320"
    height="auto">
    <UILabel
        backgroundColor="#CCC"
        width="min(auto, 100%)"
        numberOfLines="0"
        text="This text is to verify line wrapping when using an expression that contains the auto keyword."
    />
</UIView>

nicklockwood avatar Oct 17 '18 07:10 nicklockwood

@nicklockwood Thanks for the prompt reply!

I've tested your suggestion and it works great. Unfortunately, I'm having trouble applying it to my desired layout. I simplified the code example above to focus on the problem but in my real layout, I already have the UILabel wrapped by a UIView whose width depends on its children. Therefore, I can't set it to a fixed width and trying to reference it otherwise in the child's width expression will result in a circular reference.

What I'm trying to achieve is a layout where I'm showing a chat bubble next to a user's profile picture. The bubble would be a rounded rectangle (wrapping view) with a grey background that contains the user's name (top label) followed by the message text. The bubble has to grow to wrap both the user name and message.

I'm including a larger piece of the layout code below to see if an easy fix jumps out at you. Otherwise, I can try to fix it by measuring the string in code and passing the measured width as a parameter to the layout to be used in the expression.

    <UIView 
        backgroundColor="#EFF1F3"
        top="SPACING"
        left="previous.right + SPACING"
        height="auto + PADDING"
        width="auto + PADDING">

        <UILabel
            left="PADDING"
            top="PADDING"
            width="auto"
            font="medium 15"
            text="User Name"
        />

        <UILabel
            left="PADDING"
            top="previous.bottom + SPACING"
            width="min(auto, 320)"
            numberOfLines="0"
            text="This text is to verify line wrapping when using an expression that contains the auto keyword."
        />
    </UIView>

P.S.> Great job on the library! I've been looking for something like Layout for a long time and stumbled upon it by chance the other day.

Dragonspell99 avatar Oct 17 '18 21:10 Dragonspell99

@Dragonspell99 as far as I can see, there's no way to do this with the current release. I've pushed a fix to the develop branch that should make it work as you've written it above. Can you try it out and see if it solves your use-case?

nicklockwood avatar Oct 18 '18 11:10 nicklockwood

@nicklockwood

I've tested the changes that you pushed to the develop branch and it works perfectly for my use case. Thanks!

Dragonspell99 avatar Oct 18 '18 14:10 Dragonspell99

@Dragonspell99 great. I'll do some more testing and assuming everything's OK with the other test projects I'll push a release with those changes asap.

nicklockwood avatar Oct 18 '18 14:10 nicklockwood

Hi @nicklockwood ,

Although the above problem was resolved with the commit that you pushed to address the issue, I have found another interesting case that is related to the same layout so I've included it in this same issue.

Context: As described previously, this layout is for a "chat bubble". I'm trying to add the time at which the message was posted in the upper right corner of the bubble along with an icon of a clock adjacent and to the left of the time.

Aligning the UILabel that contains the time to the top right of the parent view (bubble) works fine. However, when I add the clock icon in the UIImageView, the pair of subviews shift outside and to the left of the parent UIView.

I've tested several things and I've isolated the problem which can be reproduced using the following layout which simply attempts to place an UIImageView in the top-right corner of the parent UIView.

<UIViewController>

    <macro name="PADDING" value="8"/>
    <macro name="SPACING" value="12"/>

    <UIView 
        backgroundColor="#EEF1F6"
        top="50"
        left="20"
        height="auto + PADDING"
        width="auto + PADDING">

        <UILabel
            id="AuthorLabel"
            left="PADDING"
            top="PADDING"
            width="auto"
            font="medium 15"
            text="User Name"
        />

        <UIImageView
            top="PADDING"
            right="PADDING"
            width="20"
            height="20"
            backgroundColor="#F00"
        />

        <UILabel
            left="PADDING"
            top="#AuthorLabel.bottom + SPACING"
            width="min(auto, 320)"
            numberOfLines="0"
            text="This text is to verify line wrapping when using an expression that contains the auto keyword."
        />
    </UIView>
</UIViewController>

The above layout produces a red rectangle outside and to the left of the parent UIView (gray chat bubble). Note that the same behaviour applies if an actual image is used instead of just a background colour. If the width expression of the parent UIView is changed from auto + PADDING to a constant value (e.g. 320), then the UIImageView is properly located in the top-right corner of the parent.

What's even more interesting is that if a UILabel is used instead of the UIImageView, it is correctly aligned to the top-right corner despite the above provided that the width expression is completely omitted from the node. Otherwise, the same behaviour occurs. I also tried with a UIView instead of the UIImageView and the same thing happens.

Dragonspell99 avatar Oct 19 '18 00:10 Dragonspell99

@Dragonspell99 I've not worked out why this is happening yet, but replacing right="PADDING" with left="parent.width - width - PADDING" in the UIImageView seems to fix it.

To be clear, these should be equivalent, so this is definitely a Layout bug, but the workaround should allow you to proceed for now.

nicklockwood avatar Oct 22 '18 09:10 nicklockwood

Ok. This works fine for my scenario. Thanks!

Dragonspell99 avatar Oct 24 '18 14:10 Dragonspell99