One powerful, yet sometimes rightfully feared programming technique is recursion. Many do not even use it, some don’t know what it is, but when working with hierarchies, you really have to use it. Let’s take a look at recursion and apply it to a common UI case where you need it.
Download and open the exercise file. I put a playground inside the project to show you some simple recursion. The classic definition of recursion is
recursion: See Recursion
Recursion is calling yourself. However that’s not quite accurate. The most basic recursion is this.
func factorial(_ n:Int)->Int{ return factorial(n-1) * n }
This calls itself with one less for n
every time. You’ll notice that Xcode has problems with this, and does warn you when you do not have a well-formed recursion. Something like this will not run truly forever, but will run until you run out of memory or out of range of your type. The definition of a well formed recursion is better written
Recursion: See Recursion until something happens.
There is some condition where we stop the recursion. For example, I can add a condition that if n
is greater or equal to 1, the function returns a value of 1.
func factorial(_ n:Int)->Int{ if n >= 1{ return factorial(n - 1) * n } else{ return 1 } }
The error disappears. We can run the function for 5 and get 120.
This is great, but where it is the most practical is traveling hierarchy or tree structures. Recursion is basically making a smaller identical problem and solving that, until there’s no more problems. One place you can see this is in a really messy storyboard I wrote.
I’ve got 20 buttons and I decide they would look better with a drop shadow and rounded corners, like I’ve shown you in other tips. I have them in three stack views and in two of the stack views I have stack views of one to three buttons.
I’m not going to make 20 outlets and change all of them. Instead, I’ll use the view hierarchy and some recursion.
Got to the view controller, and you’ll see I stubbed out code for us. There a’s function findButton
which takes two parameters: a view and a level, which I defaulted to 0
func findButton(view:UIView,level:Int = 0){ print("Subview count: \(view.subviews.count) Level: \(level)") }
Right now all it does is print out the number of subviews and level. I’ll change this into a recursive function to search for Buttons to change.
I’ll start with the limiting factor. This time if a view has subviews, this is the subview count is greater than 0, I’ll look at each subview for more subviews.
if view.subviews.count >= 0{ }
I could do a recursive look at that array of subviews, but for ordered items I tend to use a for. Iteration simplifies tracing the recursion, and is a bit less intense on memory in large cases. So I’ll add this
for subview in view.subviews{ }
However within this loop, I will check each subview for subviews recursively, indicating I’ve dropped a level in the tree by incrementing the level
findButton(view: subview,level:level + 1)
At some point, I’ll hit a view without subviews. The if
will fail, and that’s a single view. For this I’ll check for a button
if let button = view as? UIButton{ }
And I’ll set the button to have the level for title, as a rounded button with a shadow.
button.setTitle("Level \(level)", for: .normal) button.setTitleColor(.white, for: .normal) viewFormat(view: button)
We’ll call this in view did layout subviews so rounded corners work right.
override func viewDidLayoutSubviews() { findButton(view: self.view) }
Change your simulator to an iPad pro 9.7″. Run this and you get rounded buttons with shadows.
While I changed everything, This technique can find a specific view if you have some identifying information. Of course it is not limited to views, but nested arrays or dictionaries can be traversed easily using a recursive method like this.
The Whole Code
// // ViewController.swift // RecursionDemo // // // A exercise file for iOS Development Tips Weekly // by Steven Lipton (C)2018, All rights reserved // For videos go to http://bit.ly/TipsLinkedInLearning // For code go to http://bit.ly/AppPieGithub // import UIKit class ViewController: UIViewController { func viewFormat(view:UIView){ view.layer.shadowOpacity = 0.9 view.layer.shadowRadius = 5.0 view.layer.shadowColor = UIColor.black.cgColor view.layer.shadowOffset = CGSize(width: 3, height: 3) if view.frame.width > view.frame.height{ view.layer.cornerRadius = view.frame.height / 2.0 } else { view.layer.cornerRadius = view.frame.width / 2.0 } } func findButton(view:UIView,level:Int = 0){ print("Subview count: \(view.subviews.count) Level: \(level)") if view.subviews.count >= 0{ for subview in view.subviews{ findButton(view: subview,level: level + 1) } } if let button = view as? UIButton{ button.setTitle("Level \(level)", for: .normal) button.setTitleColor(.white, for: .normal) viewFormat(view: button) } } override func viewDidLayoutSubviews() { findButton(view: self.view) } override func viewDidLoad() { super.viewDidLoad() } }
Leave a Reply