Segue Actions

If you’ve been working with View controllers for a while, you’ve probably dealt with prepareForSegue. In Xcode 11, there’s a new way to handle this that makes a little more sense: Segue actions. Let’s take a look at this feature.  

If you download the storyboard, you’ll find I set one up for you. It is just a button that increments a counter on a detail view. Delete the segue between the two. I’ll use the button as navigation only. I’ll control-drag to the detail controller, and use a show segue. 

Open up the assistant editor. Control drag from the segue to the code for ViewController somewhere below viewDidLoad. Release the button. You’ll be asked for a connection. Name it addOneSegue. This method is a segue action, which triggers on a segue. It does much of what prepareForSegue does without some of the overhead. Add the action, and close up the assistant editor. Head over to the View Controller code. 

I’m going to make a counter. I’ll need a counter property here

var count = 1 

You’ll be more specific here than in a prepareForSegue. You make an instance of the controller, set its properties, and return the controller. No messing with id’s or anything else. Let’s try this.

   @IBSegueAction func addOneSegue(_ coder: NSCoder) -> DetailViewController? {

First, I’ll make an instance. Because this is instantiating to coder, this is optional. So use an if let here. 

    if let detailVC = DetailViewController( coder: coder){

If the controller instantiates, I’ll add one and set that in the detail. 

count += 1
detailVC.count = self.count

Then return the view controller.

return detailVC    
}

 And finally, if it doesn’t work, return nil.     

return nil
}

Over at the detail, we’re all set up with viewDidLoad updating the count. Run this.

You’ll get the button. Tap it. You get a count. Hit Back, then the button. Again, it updates. 

Segue actions save time in finding and naming segues and destination controllers. This new feature is a small gift of SwfitUI to UIKit. You’ll use this to get SwiftUI into storyboards, but I’ll discuss that at another time and in my Course SwiftUI Essential Training. Do remember outlet actions instantiate the instance, but do not load it. You still can’t assign a value to the label directly. 

I find this a better alternative than prepare for segue in delegation between my view controllers. 

The Whole Code

You can find the code for this lesson in GitHub here:

Use Sets in Swift

Of the most underrated collection types is sets. Sets are an unordered collection with unique values. Let’s take a look at what you can do with sets. 

Download the exercise files and you’ll find I created  playground with a Struct called Pizza. I’ve made two pizzas for you. Currently, this is all arrays, but does it have to be arrays? Ask yourself three questions:

  • Do I need ordered values?
  • Do I need unique values
  • Do I need access directly to any value? 

If you need ordered values and direct access, you’ll want an array. If you want unique values and don’t care about order or access, you’ll want a set. A Pizza is a good example of where a set works well. An ingredient is an ingredient. There is no good reason to list it twice, and it also has no particular order for toppings. I don’t need to access only one ingredient.  That’s a set. Sets are stored and accessed more eifficiently than array or dictionaries, so if you can use a set, do so. 

Lets convert this struct to sets. For the two constants on top, I add Set and the compiler will implicitly make this a set of strings. For cheese and toppings, I have to get more explicit. 

var cheeses: Set<String>
var toppings: Set<String>

I added the type of the set in angle brackets. Run what you got and it still works.  The pizzas below still compile. 

You can enumerate a set using for. For example: 

for cheese in quattroFormaggi.cheeses{
    print(cheese)
}

You are not guaranteed the order however.  If I want them sorted, I can use the sorted method

for cheese in quattroFormaggi.cheeses.sorted(){
    print(cheese)
}

The core of sets is membership testing. I can check for the existence of something easily. 

chicago.toppings.contains("Sausage")

returns true

quattroFormaggi.toppings.contains("Sausage")

Return false.

I can look for a bigger set too. I have a set of toppings here. I can check it against one of my pizzas. 

 margheritaDoc.toppings.isSubset(of: toppings)

That comes out false. I can find the missing ingredients quickly. 

margheritaDoc.toppings.subtracting(toppings)

And I find I have whole tomatoes instead of crushed here. 

Are any authentic  cheeses also toppings?  I can use an intersection to check that.

margheritaDoc.toppings.intersection(margheritaDoc.authenticCheese)

That gives me one case. If I want true or false I can use isEmpty. To look for the empty set. 

margheritaDoc.toppings.intersection(margheritaDoc.authenticCheese).isEmpty

What if I want to check against two pizzas. I can union the pizzas and check that against the list. 

let cheeses =  quattroFormaggi.cheeses.union(margheritaDoc.cheeses)
cheeses.intersection(quattroFormaggi.authenticCheese)

So What of the two lists are excluded?  

cheeses.symmetricDifference(quattroFormaggi.authenticCheese)

 I for one would consider all that Authentic. 

In this simple example you can see that if you don’t care about order and have unique values, Sets may be a an efficient way of storing data.. 

The Whole Code

You can download this Swift playground from GitHub here: Tips_08_01_Sets_End

struct Pizza{
    let authenticCheese: Set = ["Bufalo","Fior de late","Gorgonzola","Mozzarella","Parmigiano"]
    let authenticIngredients: Set = ["Basil","Peppers","Tomatoes", "Basil", "Oregano"]
    enum Crust{
        case thin,thick,pan,lavosh,potPie
    }

    var crust : Crust
    var cheeses: Set<String>
    var toppings: Set<String>
}

let margherita = Pizza(crust: .thin, cheeses: ["Mozzarella"], toppings: ["Basil","Tomatoes","Parmigiano","Oil"])

let margheritaDoc = Pizza(crust: .thin, cheeses: ["Bufala"], toppings: ["Basil","Tomatoes","Parmigiano","Oil"])

let chicago = Pizza(crust: .pan, cheeses: ["Mozzarella"], toppings: ["Pizza Sauce","Sausage"])

let quattroFormaggi = Pizza(crust: .thin, cheeses: ["Fontina","Gorgonzola","Mozzarella","Parmigiano"], toppings: ["Crushed Tomatoes","Basil","Oil"])

for cheese in quattroFormaggi.cheeses.sorted(){
    print(cheese)
}

let toppings: Set = ["Crushed Tomatoes","Basil","Oil","Chicken","BBQ Sauce","Red Onions","Parmigiano", "Peppernoi","Prociutto","Pineapple","Canadian Bacon"]

chicago.toppings.contains("Sausage")

quattroFormaggi.toppings.contains("Sausage")

margheritaDoc.toppings.isSubset(of: toppings)

margheritaDoc.toppings.subtracting(toppings)

margheritaDoc.toppings.intersection(margheritaDoc.authenticCheese).isEmpty

let cheeses =  quattroFormaggi.cheeses.union(margheritaDoc.cheeses)

cheeses.intersection(quattroFormaggi.authenticCheese)

cheeses.symmetricDifference(quattroFormaggi.authenticCheese)

			
			

Strings in Swift: Going back to BASICs

A few tips ago, we went under the hood with unicode Characters and their relationship to the Swift String type. For most, that’s great theory, but how does it apply to strings, not characters? When I started programming back in the 1980’s, I had three string functions in BASIC: RIGHT$, LEFT$, and MID$. Let’s create a simple extension to String that will let you use MID$ using string ranges, then discuss right$ and left$ equivalents, and how these get returned. 

Download the exercise files. head to the embedded playground and hide everything but the playground. set run to manual if necessary.

You’ll find I started an extension. 

extension String{
}

I also added an example string you run the playground, you’ll find the string has a lot of grapheme clusters of arrows and emoji.

Strings in Swift do not use integer string indexes to access characters due to grapheme clusters. Instead of arrays, strings are an ordered collection that uses relative distances from two constants, startIndex and endIndex

For my function I’ll need those string indexes. I made you a function to get those, returning an optional value. If we are out of range, it returns nil.  

func index(_ position:Int)->String.Index!{
        if position < 0 || position > self.count {return nil}
        return self.index(self.startIndex, offsetBy: position)
    }

For my midString method, I want a string starting at one character position and going for a length,

func midString(from index:Int, length:Int)->String!{

}

First I’ll check if my starting positions is a valid one, returning nil if not a valid position: 

if let startPosition = self.index(index){

}
return nil

String Indexes take ranges, so I use a closed range from the startPostion to an endPosition. I calcualte the endPosition by adding the length, but still have to take 1 off the length. That’s an optional so I’ll optionally chain it this way. 

if let endPosition = self.index(index + length - 1){

}

Then I should be able to return the range.  

 return self[startPosition...endPosition]

Except that gets me an error. When you take something from a String range, you don’t get a string, but a substring. Substrings are excellent for memory allocation but extremely unstable, since they are really a set of pointers to the sub string within the parent string. As soon as possible with a substring, instantiate it as a string. 

return String(self[startPosition...endPosition])

Now to test all this with a string with extended grapheme clusters

print(yummy.midString(from: 3, length: 3))

And I get

I’ll change this to an invalid length

print(yummy.midString(from: 3, length: 35))

Which returns nil. That works as we wanted. 

Prefix and Suffix

Ranges work in strings to get you substrings. If you want the beginning or end of a string, you use prefix and suffix, which also return substrings. 

I can use

 print(yummy.prefix(3))

to get the substring

Or I can use 

print(yummy.suffix(3)

And get the substring.

If I overflow these, 

yummy.suffix(35)
yummy.prefix(35)

I get the full string, but as a substring. All Of what I did here is on grapheme clusters. Try one with both

yummy.suffix(5).prefix(1)

returns

There are other ways of breaking apart a string if you want the diacritical marks to be separate from the associated glyph, but for most applications, this is all you need. 

The Whole code

Here’s the code for the playground If you don’t want to download it from GitHub.

 var yummy = "D\u{1f369}ugh\u{20d7}n\u{20ed}uts"
 extension String{
      func index(_ position:Int)->String.Index!{
         if position < 0 || position > self.count {return nil}
         return self.index(self.startIndex, offsetBy: position)
     }
     func midString(from index:Int, length:Int)->String!{
         if let startPosition = self.index(index){
             if let endPosition = self.index(index + length - 1){
                 return String(self[startPosition...endPosition])
             }
         }
         return nil
     }
     
 }
 

 //Now to test all this with a string with extended grapheme cluster
 yummy
 yummy.midString(from: 3, length: 3)
  yummy.midString(from: 3, length: 35)
 

 yummy.prefix(3)
  yummy.suffix(3)
  yummy.prefix(35)
  yummy.prefix(35)
  yummy.suffix(5).prefix(1)
  
  
  
  
  
 

Launch an Alert from a Closure Safely

Your app can get into problems when you launch UI from others threads, such as closures. 

For example, you  might have an app that going to ask for permissions for things like photos, notifications, or location data. The system usually handles those, but you might want to be even more exclusive on what to use, and use your own alert. In this tip, I’ll show you how to launch an alert from a closure. 

I’ll use an slightly modified exercise file from the iOS and watchOS App Development:Notifications course I have in the LinkedIn Learning library. If you haven’t seen the course yet, notifications need permissions, and in that exercise file I show how to set up permissions. Since the demo app requires notifications, if the user prohibits notifications, I want the app to tell them that’s a problem. 

I’ll use this method I wrote in an earlier tip to launch the settings

let settingsAction = UIAlertAction(title: "Settings", style: .default) { (action) in
            if let appSettings = URL(string: UIApplication.openSettingsURLString) {
                UIApplication.shared.open(appSettings, options: [:], completionHandler: nil)
            }
        }

I’ll use this alert in my user permissions. I have two actions in my app that send notifications. Before they do, they check if they are allowed to using the getNotificationSettings method. That method  runs  a closure. In that closure, add the  code to run the alert if denied or not determined on schedulePizza and makePizza UIActions.

if status == .denied || status == .notDetermined{
    self.accessDeniedAlert()
    return
}

I’ll erase content and settings on my iPhone XR simulator to have a clean slate for permissions. Once it is ready, I’ll run the app. 

I’ll get the permissions alert from the system. I’ll hit Don’t allow, and try to schedule a pizza. We get the alert.

However, look at the console in Xcode.

2019-06-19 06:42:58.245095-0500 Huli Pizza Notification[14948:369926] [Assert] Cannot be called with asCopy = NO on non-main thread.

You have an error message, one I would not ignore. 

The app is running a UI thread of an alert on a non UI thread. The closure is creates a new thread, but alerts really need to be on the main thread where all the User interface is. There’s a huge problem with this that might not be easy to track if you are not aware of it. Because this alert is not running on the main thread, the main thread barrels on, running code without the correct settings. You can get very confused tracking down what happened with that. 

You must run UI on the main thread so the rest of the system responds correctly. Fortunately that is easy. Just state what code you want running on the main thread.   You can assign something to the main thread with he main singleton of the DispatchQueue class. I’ll change the code for a denied permission in schedulePizza and makePizza to this:

if status == .denied || status == .notDetermined{
    DispatchQueue.main.async {
        self.accessDeniedAlert()
    }
    return
}

Clean and Run again, and now we don’t get the error message.  I’m using an example from a notification, but anywhere you plan to call an object on the main thread, such as presenting an alert or changing a label, but doing so from a closure, make sure you assign the object to the main thread before doing so.  

The Not So Whole Code

This week’s project has a big one backing it, so I’m only going to show you the view controller here. Download the project from Github for the full project.

//
//  ViewController.swift
//  Huli Pizza Notification
//
//  Created by Steven Lipton on 11/23/18.
//  Copyright © 2018 Steven Lipton. All rights reserved.
//

import UIKit
import UserNotifications
// a global constant
let pizzaSteps = ["Make pizza", "Roll Dough", "Add Sauce", "Add Cheese", "Add Ingredients", "Bake", "Done"]


class ViewController: UIViewController {
    var counter = 0
   
    @IBAction func schedulePizza(_ sender: UIButton) {
        UNUserNotificationCenter.current().getNotificationSettings { (settings) in
            let status = settings.authorizationStatus
            if status == .denied || status == .notDetermined{
                DispatchQueue.main.async {
                    self.accessDeniedAlert()
                }
                return
            }
            self.introNotification()
        }
    }
    
    
    @IBAction func makePizza(_ sender: UIButton) {
        UNUserNotificationCenter.current().getNotificationSettings { (settings) in
            let status = settings.authorizationStatus
            if status == .denied || status == .notDetermined{
                DispatchQueue.main.async {
                   self.accessDeniedAlert()
                }
                return
            }
            self.introNotification()
        }
        
    }
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        navigationController?.isNavigationBarHidden = true
    }

    //MARK: - Support Methods
    
    // A function to print errors to the console
    func printError(_ error:Error?,location:String){
        if let error = error{
            print("Error: \(error.localizedDescription) in \(location)")
        }
    }
    
    //A sample local notification for testing
    func introNotification(){
        // a Quick local notification.
        let time = 15.0
        counter += 1
        //Content
        let notifcationContent = UNMutableNotificationContent()
        notifcationContent.title = "Hello, Pizza!!"
        notifcationContent.body = "Just a message to test permissions \(counter)"
        notifcationContent.badge = counter as NSNumber
        //Trigger
        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: time, repeats: false)
        
        //Request
        let request = UNNotificationRequest(identifier: "intro", content: notifcationContent, trigger: trigger)
        //Schedule
        UNUserNotificationCenter.current().add(request) { (error) in
            self.printError(error, location: "Add introNotification")
        }
    }
    //An alert to indicate that the user has not granted permission for notification delivery.
    func accessDeniedAlert(){
        // presents an alert when access is denied for notifications on startup. give the user two choices to dismiss the alert and to go to settings to change thier permissions.
        let alert = UIAlertController(title: "Huli Pizza", message: "Huli Pizza needs notifications to work properly, but they are currently turned off. Turn them on in settings.", preferredStyle: .alert)
        let okayAction = UIAlertAction(title: "Dismiss", style: .default, handler: nil)
        let settingsAction = UIAlertAction(title: "Settings", style: .default) { (action) in
            if let appSettings = URL(string: UIApplication.openSettingsURLString) {
                UIApplication.shared.open(appSettings, options: [:], completionHandler: nil)
            }
        }
        alert.addAction(okayAction)
        alert.addAction(settingsAction)
        present(alert, animated: true) {
        }
    }
}

Change UIColors to RGB and HSB colors

One dilemma you’ll find when working with colors is switching between color systems. There’s two you’ll most often be using: the Red-Green-Blue or RGB and Hue-Saturation-Brightness or HSB. Download the Exercise file and run. It will give you the HSB value, but what if you want a RGB Value for that color? What if you want the values for any UIColor? Let’s look at some special methods for that. 

In the exercise files, go to  ColorModel.swift and the ColorEntry class. I’ll add some methods here to do all this.  

I stubbed a method  rgbString here for  you with a few variables: 

func rgbString()->String{
        var rgb = ""
        var red:CGFloat = 0
        var green:CGFloat = 0
        var blue:CGFloat = 0
        var alpha:CGFloat = 0
        return rgb

There’s a UIColor method getRed that gets you the red green, blue and alpha components of a color.  It works a bit differently than most methods though. Its parameters are unsafe mutable pointers, and it returns a Bool if it was successful. I’ll put it in an if statement to catch any errors  like this: 

if !color.getRed(&red, green: &green, blue: &blue, alpha: &alpha){
           
        }

Unsafe pointers work a little backward. You put a strong  pass-through variable for the parameter prefixed by the ampersand (&). I invert the bool here so I’ll deal with error conditions in the if clause. I’ll just return the blank rgb value in this case, which

if !color.getRed(&red, green: &green, blue: &blue, alpha: &alpha){
return rgb
}

If successful, I’ll make a string to return the RGB color as a String

rgb = String(format:"R:%04.3f G:%04.3f B:%04.3f",red,green,blue)

I can do the same  with HSB Colors wit the getHue method. I already have properties for this in my model, I just need to add the alpha. The code for this is very similar. 

func hsbString()->String{
        var hsb = ""
        var alpha:CGFloat = 0
        if !color.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha){
            return hsb
        }
        
        hsb = String(format:"H:%04.3f S:%04.3f B:%04.3f",hue,saturation,brightness)
        return hsb
    }

I can now set my color name by these two strings. I’ll make an initializer that creates the name from the strings: 

init(color:UIColor){
        self.color = color
        self.name = hsbString() + "\n" + rgbString()
    }

I’ll also change the name on my first initializer to include both: 

init(name:String,color:UIColor){
        self.color = color
        self.name = name + "\n" + hsbString() + "\n" + rgbString()
    }

I don’t need to use the first initializer in the hues method of color model, I can use the second, so I’ll change that: 

//let name = String(format:"H:%04.3f S:1.0 B:1.0 ",hueValue)
let colorEntry = ColorEntry(color: color)

Run the app. You’ll see the table has changed. When you select a color, both values show up. 

You can use these methods for testing colors in many places, including pixels in images. While a little clunky with he unsafe mutable pointers, this is a relatively easy way to get get colors from one system to another. 

The (not so) Whole Code

I’ve been using the same code for the last few weeks. You can go to the last lesson to get everything but the changes made to ColorModel.swift, which is below. You can also download the completed project from Github.

//
//  ColorModel.swift
//  ColorPicker
//
//
//  An 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 ColorEntry{
    var name:String = ""
    var color:UIColor
    var hue:CGFloat = 0.0
    var brightness:CGFloat = 0.5
    var saturation:CGFloat = 1.0
    
    init(name:String,color:UIColor){
        self.color = color
        self.name = name + "\n" + hsbString() + "\n" + rgbString()
    }
    
    init(color:UIColor){
        self.color = color
        self.name = hsbString() + "\n" + rgbString()
    }
    
    
    func rgbString()->String{
        var rgb = ""
        var red:CGFloat = 0
        var green:CGFloat = 0
        var blue:CGFloat = 0
        var alpha:CGFloat = 0
        
        if !color.getRed(&red, green: &green, blue: &blue, alpha: &alpha){
            return rgb
        }
        
        rgb = String(format:"R:%04.3f G:%04.3f B:%04.3f",red,green,blue)
        return rgb
    }
    
    func hsbString()->String{
        var hsb = ""
        var alpha:CGFloat = 0
        if !color.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha){
            return hsb
        }
        
        hsb = String(format:"H:%04.3f S:%04.3f B:%04.3f",hue,saturation,brightness)
        return hsb
    }
    
}


class ColorModel{
    var colors = [ColorEntry]()
    init(){
        colors = []
    }
    func hues(count:Int)->[ColorEntry]{
        colors = []
        if count <= 0 {return colors}
        for hue in 0...count{
            let hueValue = CGFloat(hue)/CGFloat(count)
            let color = UIColor(hue: hueValue, saturation: 1.0, brightness: 1.0, alpha: 1.0)
            //let name = String(format:"H:%04.3f S:1.0 B:1.0 ",hueValue)
            let colorEntry = ColorEntry(color: color)
            colors += [colorEntry]
        }
        return colors
    }
    
    func lightnessScale(hue:UIColor,count:Int){
        
    }
    
}

Use Dynamic Type

For reading ease and visual accessibility you should be using dynamic fonts whenever possible. Let’s look at an example you can get from the download files. I’ve run the Split View Controller from a previous tip in landscape on an iPad Pro 9.7 inch. Take a look at the labels on the table.

They are hard to read because they are so small. iOS has a system to make fonts bigger using dynamic type. Let’s look at a few issues with dynamic type programmatically. 

For the user, they need to change one setting to make this bigger. ON the iPad simulator, click the Home button, and Settings. Select General>Accessibility,

Then Larger Text

On the bottom is a slider which you can notch up for larger text. For even larger text you can click on the switch on top.  Make this pretty big.

That scales up the text in settings too. Go back to the app. You see bigger sizes but with white backgrounds,

The simulator has problems with this adjustment, which is why the white backgrounds. . Stop the app and re-run it.

By default, cells are dynamic text, the type called Body. Now rotate the simulator with a Command Left-Arrow, and drag out the master.  Depending on the device, you may see the text wrap or get cut off.

To make sure it wraps,   Head to the HueColorTableViewController and the cellForRowAtIndexpath method. Add the following line: 

cell?.textLabel?.numberOfLines = 0 

Setting the label property numberOfLines to 0 does two things: it allows for a variable number of lines and activates a word wrap mode. Run again, and  if you didn’t before, you’ll see our text wrap in the cells. 

I still have a static font for my detail label. Head over there and You’ll see I have a UIFont of 30-point American Typewriter. For dynamic type, You have two choices here. One is to use one of the system dynamic fonts. Add this to your code:

let dynamicFont = UIFont.preferredFont(forTextStyle: .title1)

The preferred Font method sets the font to the dynamic font title1. I’ll change the font to the dynamic font: 

colorLabel.font = dynamicFont

Run this

. This time, The font is a lot more visible, but on the system font. If I wanted a custom font, I’d have to do a few more steps. I’ll assign the colorLabel's font to  a class UIFontMetrics and its default, 

colorLabel.font = UIFontMetrics.default.scaledFont(for: font)

Finally, I need to tell the label to scale the font. 

colorLabel.adjustsFontForContentSizeCategory = true

To be sure we get word wrapping, I’ll do this again

 colorLabel.numberOfLines = 0

Run this. Change the font size to the smallest. 

You’ll see the label remains at 30 point, as the Master dynamic type in the cells shrink.

Now make the font as large as it can get and run again. The font scales.  

There is a complete course in the course library on accessibility which deep dives this topic and shows you the storyboard version of all this.  It is something you should check out for better accessibility and adaptability of your user interface.  

The Whole Code

Here’s the code for this week’s project. You can download the full project at Github.

HueColorTableViewController.swift

//
//  HueColorTableViewController.swift
//  ColorPicker
//
//
//  An 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

protocol HueColorTableViewControllerDelegate{
    func didSelectColor(color:ColorEntry)
}


class HueColorTableViewController:UITableViewController{
    var delegate:HueColorTableViewControllerDelegate! = nil
    var colors:[ColorEntry] = ColorModel().hues(count: 12)
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return colors.count
    }
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        var cell = tableView.dequeueReusableCell(withIdentifier: "cell")
        if cell == nil {
            cell = UITableViewCell(style: .default, reuseIdentifier: "cell")
        }
        let row = indexPath.row
        cell?.contentView.backgroundColor = colors[row].color
        cell?.textLabel?.text = colors[row].name
        cell?.textLabel?.numberOfLines = 0
        return cell!
    }
    
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let row = indexPath.row
        title = colors[row].name
        delegate.didSelectColor(color: colors[row])
    }
    
    override func loadView() {
        super.loadView()
    }
    

}

ColorDetailViewController.swift

//
// ColorDetailViewController.swift
// ColorPicker
//
//  An 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 ColorDetailViewController: UIViewController, HueColorTableViewControllerDelegate  {
    let colorLabel = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()
        addLayout()
        // Do any additional setup after loading the view.
    }
    
    //MARK:- Delegates
    func didSelectColor(color: ColorEntry) {
        view.backgroundColor = color.color
        colorLabel.text = color.name
    }
    
    //MARK:- Layout
    // All layout methods go here.
    func addLayout(){
        colorLabel.text = "Color Detail"
        let font = UIFont(name: "AmericanTypewriter", size: 30)!
       //let dynamicFont = UIFont.preferredFont(forTextStyle: .title1)
        colorLabel.font = UIFontMetrics.default.scaledFont(for: font)
        colorLabel.adjustsFontForContentSizeCategory = true
        colorLabel.numberOfLines = 0
        colorLabel.backgroundColor = .lightGray
        colorLabel.textAlignment = .center
        colorLabel.numberOfLines = 0
        view.addSubview(colorLabel)
        colorLabel.translatesAutoresizingMaskIntoConstraints = false
        var constraints = [NSLayoutConstraint]()
        constraints += [NSLayoutConstraint(item: colorLabel, attribute: .leading, relatedBy: .equal, toItem: view.safeAreaLayoutGuide, attribute: .leading, multiplier: 1.0, constant: 0)]
        constraints += [NSLayoutConstraint(item: colorLabel, attribute: .trailing, relatedBy: .equal, toItem: view.safeAreaLayoutGuide, attribute: .trailing, multiplier: 1.0, constant: 0)]
        constraints += [NSLayoutConstraint(item: colorLabel, attribute: .top, relatedBy: .equal, toItem: view.safeAreaLayoutGuide, attribute: .top, multiplier: 1.0, constant: 0)]
        constraints += [NSLayoutConstraint(item: colorLabel, attribute: .height, relatedBy: .equal, toItem: view.safeAreaLayoutGuide, attribute: .height, multiplier: 1 / 9, constant: 0)]
        view.addConstraints(constraints)
    }
}

ColorModel.swift

//
//  ColorModel.swift
//  ColorPicker
//
//
//  An 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 ColorEntry{
    var name:String = ""
    var color:UIColor
    var hue:CGFloat = 0.0
    var brightness:CGFloat = 0.5
    var saturation:CGFloat = 1.0
    
    init(name:String,color:UIColor){
        self.color = color
        self.name = name
    }
}


class ColorModel{
    var colors = [ColorEntry]()
    init(){
        colors = []
    }
    func hues(count:Int)->[ColorEntry]{
        colors = []
        if count <= 0 {return colors}
        for hue in 0...count{
            let hueValue = CGFloat(hue)/CGFloat(count)
            let color = UIColor(hue: hueValue, saturation: 1.0, brightness: 1.0, alpha: 1.0)
            let name = String(format:"H:%04.3f S:1.0 B:1.0 ",hueValue)
            let colorEntry = ColorEntry(name: name, color: color)
            colors += [colorEntry]
        }
        return colors
    }
    
    func lightnessScale(hue:UIColor,count:Int){
        
    }
    
}

Programmaticall​y Add Split View Controllers

An often ignored but rather powerful View Controller is the UISplitViewController. You can make one from a template and the storyboard, but I often skip both and do it programmatically, which is especially good when I’m prototyping in a playground.  Let’s give it a try using the exercise files I’ve been using for the past few tips. There we added a color table. Split view controllers have a master, which I’ll make the color table, and the detail, which will tell us the color name, and show the color. 

Head over to the AppDelegate in this project. Split View controllers should be your root view, and you have two view controllers be the roots for the detail and master view.  I usually embed the root view controllers in navigation controllers, so I can add view controllers off that. I did that for the master already. I only need the detail, which I can do like this:  

let colorDetailViewController = ColorDetailViewController()
let detailNavigationViewController = UINavigationController(rootViewController: colorDetailViewController)

 I’ll instantiate a split view controller. 

let splitViewController = UISplitViewController()

You assign the master and detail to the viewControllers property, which is an array of two elements. The first element of the array is the master and the second the detail.  

splitVC.viewControllers = [navigationVC,colorDetailVC]

 I  like doing one more thing to control the width of the master view. I’ll set the property preferredPrimaryColumnWIdthFraction to a proportion. I’ll set this one to 1/3.  

splitVC.preferredPrimaryColumnWidthFraction = 1/3

Finally, assign the splitViewController to the window‘s rootViewController

window?.rootViewController = splitViewController

Build and run this on an iPhone XR simulator. You’ll just get a single view of the detail. On a compressed width, such as you get in portrait, you get only one of the two views.

On a regular width you’ll get both. Rotate the simulator with a command-left arrow. In Portrait, you see the master and detail.

  If you tap a color, it still doesn’t do anything in the detail. I’d like to show the color on the detail when selected from the mater. These are two view controllers without any way to communicate with each other. I’ll use delegation here. 

Stop the app. If you go to HueColorTableViewController, you’ll see I made a protocol and delegate.  I’ll  add the delegate’s method to the code here in did select 

delegate.didSelectColor(color: colors[row])

In the ColorDetailViewController, I’ll adopt the delegate

UIViewController,HueColorTableViewControllerDelegate  {

I  added a callback to change the background color and display the name. 

Go back to AppDelegate and add a line to direct the delegate correctly. 

colorTVC.delegate = colorDetailVC

Run that, and you get the same split view controller, but select a color and it appears.

That’s the basics of adding a split view controller to a project programmatically. 

The Whole Code

Below you’ll find the code for this project. You can also download the code from GitHub here.

AppDelegate.swift

//
//  AppDelegate.swift
//  ColorPicker
//
//
//  An 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

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?


    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        window = UIWindow(frame: UIScreen.main.bounds)
        
        // Instantiate root view controllers
        let hueColorTableViewController = HueColorTableViewController()
        let colorDetailViewController = ColorDetailViewController()
        hueColorTableViewController.delegate = colorDetailViewController
        
        
        // Embed in navigation controllers
        let masterNavigationViewController = UINavigationController(rootViewController: hueColorTableViewController)
        let detailNavigationController = UINavigationController(rootViewController: colorDetailViewController)
        
        
        // Embed in Split View controller
        let splitViewController = UISplitViewController()
        splitViewController.viewControllers = [masterNavigationViewController,detailNavigationController]
        splitViewController.preferredPrimaryColumnWidthFraction = 1/3
        
        // Root view controller of window
        window?.rootViewController = splitViewController
        window?.makeKeyAndVisible()
        return true
    }

}


HueColorTable.swift

//
//  HueColorTableViewController.swift
//  ColorPicker
//
//
//  An 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

protocol HueColorTableViewControllerDelegate{
    func didSelectColor(color:ColorEntry)
}


class HueColorTableViewController:UITableViewController{
    var delegate:HueColorTableViewControllerDelegate! = nil
    var colors:[ColorEntry] = ColorModel().hues(count: 12)
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return colors.count
    }
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        var cell = tableView.dequeueReusableCell(withIdentifier: "cell")
        if cell == nil {
            cell = UITableViewCell(style: .default, reuseIdentifier: "cell")
        }
        let row = indexPath.row
        cell?.contentView.backgroundColor = colors[row].color
        cell?.textLabel?.text = colors[row].name
        cell?.textLabel?.numberOfLines = 0
        return cell!
    }
    
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let row = indexPath.row
        title = colors[row].name
        delegate.didSelectColor(color: colors[row])
    }
    
    override func loadView() {
        super.loadView()
    }
    

}

ColorDetailViewController.swift

//
// ColorDetailViewController.swift
// ColorPicker
//
//  An 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 ColorDetailViewController: UIViewController, HueColorTableViewControllerDelegate  {
    let colorLabel = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()
        addLayout()
        // Do any additional setup after loading the view.
    }
    
    //MARK:- Delegates
    func didSelectColor(color: ColorEntry) {
        view.backgroundColor = color.color
        colorLabel.text = color.name
    }
    
    //MARK:- Layout
    // All layout methods go here.
    func addLayout(){
        colorLabel.text = "Color Detail"
        let font = UIFont(name: "AmericanTypewriter", size: 30)!
        colorLabel.font = font
        colorLabel.backgroundColor = .lightGray
        colorLabel.textAlignment = .center
        colorLabel.numberOfLines = 0
        view.addSubview(colorLabel)
        colorLabel.translatesAutoresizingMaskIntoConstraints = false
        var constraints = [NSLayoutConstraint]()
        constraints += [NSLayoutConstraint(item: colorLabel, attribute: .leading, relatedBy: .equal, toItem: view.safeAreaLayoutGuide, attribute: .leading, multiplier: 1.0, constant: 0)]
        constraints += [NSLayoutConstraint(item: colorLabel, attribute: .trailing, relatedBy: .equal, toItem: view.safeAreaLayoutGuide, attribute: .trailing, multiplier: 1.0, constant: 0)]
        constraints += [NSLayoutConstraint(item: colorLabel, attribute: .top, relatedBy: .equal, toItem: view.safeAreaLayoutGuide, attribute: .top, multiplier: 1.0, constant: 0)]
        constraints += [NSLayoutConstraint(item: colorLabel, attribute: .height, relatedBy: .equal, toItem: view.safeAreaLayoutGuide, attribute: .height, multiplier: 1 / 9, constant: 0)]
        view.addConstraints(constraints)
    }
}

ColorModel.swift

//
//  ColorModel.swift
//  ColorPicker
//
//
//  An 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 ColorEntry{
    var name:String = ""
    var color:UIColor
    var hue:CGFloat = 0.0
    var brightness:CGFloat = 0.5
    var saturation:CGFloat = 1.0
    
    init(name:String,color:UIColor){
        self.color = color
        self.name = name
    }
}


class ColorModel{
    var colors = [ColorEntry]()
    init(){
        colors = []
    }
    func hues(count:Int)->[ColorEntry]{
        colors = []
        if count <= 0 {return colors}
        for hue in 0...count{
            let hueValue = CGFloat(hue)/CGFloat(count)
            let color = UIColor(hue: hueValue, saturation: 1.0, brightness: 1.0, alpha: 1.0)
            let name = String(format:"H:%04.3f S:1.0 B:1.0 ",hueValue)
            let colorEntry = ColorEntry(name: name, color: color)
            colors += [colorEntry]
        }
        return colors
    }
    
    func lightnessScale(hue:UIColor,count:Int){
        
    }
    
}