Inner Love

Never refuse the pain which sets in your heart. Like the wings of a butterfly, set it free to express itself. Being honest and true connects oneself to their heart, which eventually leads to healing…

Smartphone

独家优惠奖金 100% 高达 1 BTC + 180 免费旋转




How to make smart UIStackView spacers

Have you ever needed to create a StackView that has a different spacing between each of its arranged subviews? Well, if you have and your project needs to support older OS than iOS 11 (you can do it natively in iOS 11+) you probably just created an empty subviews with defined width/height and added it between other subviews. But hey, there’s a catch.

What if you want to hide one of the original arranged subviews (let’s say the middle one)? After hiding it, there will most likely be an undesirable spacing because there are empty spacer subviews from both of its sides. So you have to hide one of the empty spacer subviews too, which is probably something you want to avoid to keep your code clean. In this blogpost I will show you how to implement this a smarter way, so your spacer view automatically hides with your content view.

The idea is to add a spacer that is connected to the content view and hides with it. We will achieve this by creating an extension on UIView (which would be our content view) with function that creates a spacer view and returns it. Inside a function, a spacer view will observe content view’s “hidden” property and will react on any changes of this property. The function will take parameters to distinguish size, axis and priority of the spacer.

Here is the outline of the function we are gonna implement:

And you will create such a spacer from your ViewController similar to this:

I am going to show you a working implementation that depends on ReactiveSwift. Then I will describe other approaches I tried and why they didn’t work. The implementation also depends on SnapKit (which is autolayout framework) and of course on UIKit. So don’t forget to import those at the top of the file.

The implementation is pretty straightforward I think. Inside a “createSpacer” function you create a spacer, which is UIView. Then you set its “isHidden” property to be initially matching with content view’s (which is self) “isHidden” property. Then you reactively bind changes of content view’s “isHidden” property to spacer’s “isHidden” property, so every time you hide or unhide content view. The spacer will automatically hide or unhide too. Note that the signal:forKeyPath function has to take “hidden” string as a parameter due to Objective-C legacy name. After that you only create a desired height or width (depending on the axis) size constraint of the spacer using SnapKit framework and return the spacer.

Delete reactive binding from the previous example and change spacer view to be an instance of a new UIView subclass called SpacerView like this:

Now, create a SpacerView class that will take content view as a parameter in initializer. We will observe its “hidden” property with the new block based KVO and assign it to the “observation” variable.

It works like a charm… if you are on iOS 11. Unfortunately app crashes on iOS 10 when you try to remove your content view from the stack view, because iOS 10 is not able to remove (unregister) observers from the object. I got the same result when I tried to use the old KVO. I even tried to create associated object in UIView extension that holds a reference to both content view and spacer view and in its deinit I would unregister observers. It also didn’t work because content view deinits before its associated view deinits, thus making it impossible to remove observers.

You probably ask how ReactiveSwift does it. So it works on all iOS versions. It uses method swizzling to get into UIView’s deinit, where it unregisters observers. You can try to implement it by yourself but it is very high-level programming and can lead to a lot of undefined behavior. This all means that if we want to use smart spacers without ReactiveSwift and without method swizzling, our project has to be iOS 11+. But as I said earlier, starting in iOS 11 it is possible to do it natively with setCustomSpacing function on StackView. So there’s no need to implement these smart spacers. But if you need your project to support older iOS, importing ReactiveSwift and writing this simple extension can make your stack views way smarter.

Add a comment

Related posts:

Sobre el profundo enojo del feminismo que explota

Le he dado vueltas a este escrito pero me siento con la obligación de hacerlo porque si no lo hago me voy a carcomer por dentro, por indiferente, por ver y ver y no hacer nada, ni decir nada por…

Time is Money

This old piece contains words that can help one get a clear focus on what to concentrate on when it comes to business. Since the principles of success is similar in all cases, it makes it easy to…