Recursion and UIView Changes

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

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.