Help with iOS 15-style UIButton?


Rick Aurbach
 

Just to finish up this discussion, below is the code I ended up with for my button that is compatible with both pre-15 and post-15 code.

I think that if you subclass UIButton in your current code, (particularly if you use titleEdgeInsets and/or imageEdgeInsets, you need to test for breaking changes in iOS15.

Good luck to all.


Rick Aurbach
 
Edited

Hi, Alex.

The work-around was to force the button width to be "expectedWidth" (which is the width of the [unwrapped] text + image padding + image width + 8 points of slop (to account for button margins). If you notice, I override draw() to always make this adjustment just before calling super.draw(). The 8 its of margin was determined by experiment.

The comment about "mirroring above bounds code" just means that I was (historically) using different logic to adjust button size in the pre-15 case, and that I should probably change that code to use the same logic as the 15+ version.

Hope this helps.


Alex Zavatone
 

And what is, "mirrow above bounds code”?

On Jul 19, 2021, at 12:56 PM, Alex Zavatone via groups.io <zav@...> wrote:

mirrow above bounds code"



Alex Zavatone
 

Could you point out the specific workaround please?

On Jul 19, 2021, at 12:49 PM, Rick Aurbach via groups.io <rlaurb@...> wrote:

[Edited Message Follows]

I haven't filed a bug yet, but I will. In the meantime, I've constructed the following work around — it's a hack, but it seems to work.

    override open func draw(_ rect: CGRect) {

        drawButton()

        super.draw(rect)

    }

    

    private func drawButton() {

        if #available(iOS 15, *) {

            titleLabel?.lineBreakMode = .byTruncatingTail

            titleLabel?.numberOfLines = 1

            if let text = titleLabel?.text {

                let textSize = sizeText(string: text)

                let imageSize = imageView?.bounds.size ?? CGSize.zero

                let expectedWidth = textSize.width + textGap + imageSize.width + 8

                var currentBtnSize = self.bounds.size

                currentBtnSize = CGSize(width: max(expectedWidth, currentBtnSize.width), height: currentBtnSize.height)

                self.bounds.size = currentBtnSize

                setNeedsUpdateConstraints()

            }

        } else {

            contentHorizontalAlignment = .left

            titleLabel?.adjustsFontSizeToFitWidth = false

            titleLabel?.lineBreakMode = .byTruncatingTail

 

            let imageSize = image(for: .normal)?.size ?? .zero

            let optSize = intrinsicContentSize

#warning("consider changing this to mirrow above bounds code")

            if bounds.width < optSize.width || bounds.height < optSize.height {

                setNeedsLayout()

            }

            let actualTextWidth = optSize.width - textGap - imageSize.width - trailingGap

            imageEdgeInsets = UIEdgeInsets(top: 0.0, left: (actualTextWidth + textGap), bottom: 0.0, right: trailingGap)

            titleEdgeInsets = UIEdgeInsets(top: 0.0, left: -imageSize.width, bottom: 0.0, right: -actualTextWidth)

        }

    }



Rick Aurbach
 
Edited

I haven't filed a bug yet, but I will. In the meantime, I've constructed the following work around — it's a hack, but it seems to work.

    override open func draw(_ rect: CGRect) {

        drawButton()

        super.draw(rect)

    }

    

    private func drawButton() {

        if #available(iOS 15, *) {

            titleLabel?.lineBreakMode = .byTruncatingTail

            titleLabel?.numberOfLines = 1

            if let text = titleLabel?.text {

                let textSize = sizeText(string: text)

                let imageSize = imageView?.bounds.size ?? CGSize.zero

                let expectedWidth = textSize.width + textGap + imageSize.width + 8

                var currentBtnSize = self.bounds.size

                currentBtnSize = CGSize(width: max(expectedWidth, currentBtnSize.width), height: currentBtnSize.height)

                self.bounds.size = currentBtnSize

                setNeedsUpdateConstraints()

            }

        } else {

            contentHorizontalAlignment = .left

            titleLabel?.adjustsFontSizeToFitWidth = false

            titleLabel?.lineBreakMode = .byTruncatingTail

 

            let imageSize = image(for: .normal)?.size ?? .zero

            let optSize = intrinsicContentSize

#warning("consider changing this to mirrow above bounds code")

            if bounds.width < optSize.width || bounds.height < optSize.height {

                setNeedsLayout()

            }

            let actualTextWidth = optSize.width - textGap - imageSize.width - trailingGap

            imageEdgeInsets = UIEdgeInsets(top: 0.0, left: (actualTextWidth + textGap), bottom: 0.0, right: trailingGap)

            titleEdgeInsets = UIEdgeInsets(top: 0.0, left: -imageSize.width, bottom: 0.0, right: -actualTextWidth)

        }

    }


Jeremy Hughes
 

Can you file a bug?

Maybe they will fix it before iOS 15 is released.

Jeremy

On 19 Jul 2021, at 16:06, Rick Aurbach via groups.io <rlaurb=me.com@groups.io> wrote:

Good thought. I'll give it a try.

Re: your "do you have to use the API" remark, the simple answer is that if your deployment target is iOS 15, you ARE using the API, whether you like it or not. There is no choice in this matter. And it's even worse -- if you just carry over code that works on iOS14 to an iOS15 deployment target, the button's appearance changes. In other words, iOS 15 will break existing code as soon as you link it for iOS 15 SDK deployment.

This is seriously broken.


Alex Zavatone
 

Thanks for the heads up.  That’s crappy.

On Jul 19, 2021, at 10:06 AM, Rick Aurbach via groups.io <rlaurb@...> wrote:

Good thought. I'll give it a try.

Re: your "do you have to use the API" remark, the simple answer is that if your deployment target is iOS 15, you ARE using the API, whether you like it or not. There is no choice in this matter. And it's even worse -- if you just carry over code that works on iOS14 to an iOS15 deployment target, the button's appearance changes. In other words, iOS 15 will break existing code as soon as you link it for iOS 15 SDK deployment.

This is seriously broken.


Rick Aurbach
 

Good thought. I'll give it a try.

Re: your "do you have to use the API" remark, the simple answer is that if your deployment target is iOS 15, you ARE using the API, whether you like it or not. There is no choice in this matter. And it's even worse -- if you just carry over code that works on iOS14 to an iOS15 deployment target, the button's appearance changes. In other words, iOS 15 will break existing code as soon as you link it for iOS 15 SDK deployment.

This is seriously broken.


Alex Zavatone
 



On Jul 18, 2021, at 4:52 PM, Rick Aurbach via groups.io <rlaurb@...> wrote:

  • I tried setting .numberOfLines to 1. In fact, I do so whenever changing title content. The button sets the value back to 0 and wraps the text.
Can you set up an observer to watch and see if it’s changed and then change it back? Seems like a real hack, but it’s a thought.


Alex Zavatone
 



On Jul 18, 2021, at 4:52 PM, Rick Aurbach via groups.io <rlaurb@...> wrote:

  • I tried setting .numberOfLines to 1. In fact, I do so whenever changing title content. The button sets the value back to 0 and wraps the text.
My god, that sucks. I can’t believe this wasn’t tested.  We've actually been building custom UI classes for the past month and am familiar with the pain.  

Do you have to use the new API?


Rick Aurbach
 

The problem is unique to the new iOS 15 UIButton API. There is no problem in my logic if the deployment target < iOS15.

Specifically,
  • I tried setting a constraint on the height of the button. It is ignored — the text wrap extends outside the constrained height.
  • I tried forcing the button to be very wide. That works (i.e., no wrap), but in this case, the button extends beyond the trailing edge of the image, which leads to undesirable effects.
  • I tried setting .numberOfLines to 1. In fact, I do so whenever changing title content. The button sets the value back to 0 and wraps the text.
This is, actually, quite frustrating.


Alex Zavatone
 

I did this in UIKit by making the button’s frame.size.width the frame.size.width of the content within + appropriate space buffers.

One trick is if the text component is a UILabel, making the lines of text allowed to be 1 and the linebreak mode as you have.

Have you sent the lines of text to 1?

Let us know.

Alex Zavatone

On Jul 18, 2021, at 12:48 PM, Rick Aurbach via groups.io <rlaurb@...> wrote:

I am trying to construct a button using the new iOS15 button API. (Xcode 13ß3, UIKit & Storyboards)
 
The button has a title (aligned .leading) and an image (aligned .trailing). The button has the following layout constraints:
  • a fixed leading constraint (to a label that provides prompt text)
  • a center-vertically constraint (to that same label)
  • a trailing constraint ( >= a margin value)
What I’m trying to accomplish is that the button should adjust its width based on the title content, not wrapping until the width causes the trailing constraint to be violated.
 
What actually happens is the the button’s text wraps. It acts as if it prefers wrapping to changing its size.
 
There are mentions in the documentation about disabling text wrapping for the button, but the obvious things, such as
button.titleLabel?.lineBreakMode = .byTruncatingMiddle
don’t work.
 
Has anyone whose tried working with iOS 15 buttons have any suggestions?


Rick Aurbach
 

I am trying to construct a button using the new iOS15 button API. (Xcode 13ß3, UIKit & Storyboards)
 
The button has a title (aligned .leading) and an image (aligned .trailing). The button has the following layout constraints:
  • a fixed leading constraint (to a label that provides prompt text)
  • a center-vertically constraint (to that same label)
  • a trailing constraint ( >= a margin value)
What I’m trying to accomplish is that the button should adjust its width based on the title content, not wrapping until the width causes the trailing constraint to be violated.
 
What actually happens is the the button’s text wraps. It acts as if it prefers wrapping to changing its size.
 
There are mentions in the documentation about disabling text wrapping for the button, but the obvious things, such as
button.titleLabel?.lineBreakMode = .byTruncatingMiddle
don’t work.
 
Has anyone whose tried working with iOS 15 buttons have any suggestions?