Search code examples
iosswiftclosuressetteriboutlet

Create a generic willSet closure for styling IBOutlets in Swift


Is it possible to create some sort of generic willSet closure to style IBOutlets in a view controller?

Currently I use this code to set the tint color of UIImageView once its set. ".primary" is a a static variable on UIColor which I created via a UIColor extension:

@IBOutlet weak var someImageView:UIImageView! { 
  willSet { 
    newValue.tintColor = .primary 
  } 
}

Now I have a whole bunch of outlets which I want to style in the same way and for me that seems like a lot of duplicated code, especially if I start to apply more styling than just setting the tint so I want something more generic. I came up with:

let tintClosure: (_ newValue: UIView?) -> () = { $0?.tintColor = .primary }
@IBOutlet weak var someImageView:UIImageView! { willSet{ tintClosure(newValue) } }

Now I have to write the code for the actual tinting only once and can just call the closure in willSet. However, I wonder if there is an even more efficient way so I don't even have to call the closure with (newValue) by myself but just giving it the tintClosure.

Like if a function expects a completion handler and you have a function which fits its declaration and you just pass the name of the function instead of a closure in which you call the function.

Something really fancy like:

@IBOutlet weak var someImageView:UIImageView! { superDuperClosure(willSet) }

or

@IBOutlet weak var someImageView:UIImageView! { willSet{ crazyImplementationOfTintClosre } }

or

@IBOutlet weak var someImageView:UIImageView! { willSet.howIsThisEvenPossible }

or

@IBOutlet weak var someImageView:UIImageView! { nowWeAreTalking }

Well, maybe I'm just thinking over the top and I already found the most simplistic way of doing it. Maybe I don't have a deep enough understanding of how willSet works. So dear Swift gurus, teach me how to become a swifty developer.

Update

Based on the second suggestion from Paulo Mattos I had another idea. I created a outlet collection containing all the image views. Then I just iterate over all of them tinting each one with this nice little piece of code:

@IBOutlet var imageViewsToTint: [UIImageView]! { willSet { newValue.forEach { $0.tintColor = .primary } } }

The wonderful thing with this approach is, that you basically can create outlet collection for various purposes like one for tinting, one for text size etc. and then you can just connect the views you want to style to the appropriate collections.


Solution

  • Your solution already feels pretty minimalistc to me :) I can't come up with a shorter hack around that, sorry. This, somewhat exotic, Swift Evolution proposal could provide some help, but it was postponed for now at least.

    Let's quickly explore some alternatives beyond willSet (probably gonna be downvoted for this, anyway...):

    UIView subclass. I have a UITextField that I use across my app a lot! As such, I implemented a simple subclass and did all styling in there (instead of on each view controller which it appears). Of course, this might not be viable across all your views.

    viewDidLoad. You could group all your views in an array and then do the styling inside the viewDidLoad method. Might be more error prone than your current solution — you might miss a view here and there — but gets the job done without too much hacking ;)