Make A Toggle Button

Sometimes you need a control that doesn’t yet exist. For example, the UISwitch control is far from customizable. I’d like a toggle switch that looks more like an old-fashioned switch or pushbutton. Let’s learn the basics of making your own controls by making a UIButton into a toggle button.
Open the example file. You’ll find an empty class UIToggleButton. in the UIToggleButton.swift file. To make our toggle button I’m going to subclass UIButton, since much of what I want is there.

 class UIToggleButton:UIButton{
}

I can’t use an extension because I’m going to add more properties to the button. The most important of those will be isOn.

var isOn:Bool = false

UIButton subclasses UIControl. There is a series of tracking methods in UIControl that handles touches. For controls, you don’t use UIResponder for touch events, but these methods instead. The control will handle any target-action pairs in the class hierarchy. I’ll change the button when I stop tracking a touch using the endTracking method.

override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
}

Always call super when you use these methods.

super.endTracking(touch, with: event)

And I’ll change the state of the isOn button.

isOn = !isOn 

Head to the ViewController code. You’ll see I made a button configuration method configureToggleButton. One problem with custom controls like this is you will have to add them programmatically without more work, which I’ll cover in a future tip. I added two methods to configure the button and to lay out the button, which you can find in the example file or the listing below. I’ll use the button in the action I set up, where I’ll change the title on the toggle. This way you can see the action and the touch event can run independently, but see the effect of the touch event on the toggle.

if sender.isOn {
            sender.setTitle("On", for: .normal)
        } else {
            sender.setTitle("Off", for: .normal)
        }

Build and run. You’ll have a button in the center of the screen.

You can add more features. I’ll add images for the buttons.
I may not use an image. If I don’t I’ll want the image to be nil, and use the superclass properties, so I’ll use optionals for the image in the UIToggleButtonClass

public var onImage:UIImage! = nil 
public var offImage:UIImage! = nil

In my UIToggleButton method,  II’ll make a method updateDisplay which will change the button’s background image.

func updateDisplay() {
        if isOn {
            if let onImage = onImage{
                setBackgroundImage(onImage, for: .normal)
            }
        } else {
            if let offImage = offImage{
                setBackgroundImage(offImage, for: .normal)
            }
        }
    }

I’ll update the display once I set the image. I’ll use a property setter to do that.

var isOn:Bool = false{
    didSet{
       updateDisplay()
    }
}

I’ll do the same for the off image, so I’ll copy and paste that

var offImage:UIImage! = nil {
    didSet{
      updateDisplay()
    }
}

And the same for the on Image

var onImage:UIImage! = nil{
    didSet{
       updateDisplay()
    }
}

I added to the assets folder a toggle off and toggle on button image. Head to the ViewController. In the cofigureToggleButton, add two more lines of code.

 
toggleButton.offImage = UIImage(named: "Toggle off")
 toggleButton.onImage = UIImage(named:"Toggle on")

Build and run. Tap the button. You’ll get a cool looking button that glows when on, and dims when off.

You’ll find a few more examples of switches and toggles you can use in the downloads file. Give them a try like this switch

This is only scratching the surface of this subclass. You can add background colors the same way as images. You could add haptics to make the user feel the click of the button. You might also want to play with making some properties and methods public and private. Play around and see what else you can do.

The Whole Code

UIToggleButton.swift

//
//  UIToggleButton.swift
//  Toggle buttons and switches
//
//
//  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 UIToggleButton:UIButton{
    var isOn:Bool = false{
        didSet{
            updateDisplay()
        }
    }
    var onImage:UIImage! = nil{
        didSet{
            updateDisplay()
        }
    }
    var offImage:UIImage! = nil{
        didSet{
            updateDisplay()
        }
    }
    
    func updateDisplay(){
        if isOn {
            if let onImage = onImage{
                setBackgroundImage(onImage, for: .normal)
            }
        } else {
            if let offImage = offImage{
                setBackgroundImage(offImage, for: .normal)
            }
        }
    }
    override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
        super.endTracking(touch, with: event)
        isOn = !isOn
    }
}

ViewController.swift

//
//  ViewController.swift
//  Toggle buttons and switches
//
//
//  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 ViewController: UIViewController {
    @IBOutlet weak var basicSwitch: UISwitch!
    
   
    var toggleButton: UIToggleButton!
    
    //Action when button recieves touchUpInside
    @IBAction func didToggleButton(_ sender: UIToggleButton) {
        if sender.isOn {
            sender.setTitle("ON", for: .normal)
        } else {
            sender.setTitle("OFF", for: .normal)
        }
    }
    
    //Configure the toggle button for this app.
    func configureToggleButton()-> UIToggleButton{
        let toggleButton = UIToggleButton()
        toggleButton.offImage = UIImage(named: "Toggle off")
        toggleButton.onImage = UIImage(named: "Toggle on")
        toggleButton.setTitleColor(.black, for: .normal)
        toggleButton.titleLabel?.font = UIFont(name: "Gill Sans", size: 36)
        toggleButton.setTitle("OFF", for: .normal)
        toggleButton.addTarget(self, action: #selector(didToggleButton(_:)), for: .touchUpInside)
        return toggleButton
    }
    
    //Add the button to the layout centered on the view.
    func addToggleButton(to view:UIView) {
        toggleButton = configureToggleButton()
        view.addSubview(toggleButton)
        toggleButton.translatesAutoresizingMaskIntoConstraints = false
        var constraints = [NSLayoutConstraint]()
        constraints += [NSLayoutConstraint.init(item: toggleButton, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerXWithinMargins, multiplier: 1.0, constant: 0)]
         constraints += [NSLayoutConstraint.init(item: toggleButton, attribute: .centerY, relatedBy: .equal, toItem: view, attribute: .centerYWithinMargins, multiplier: 1.0, constant: 0)]
         constraints += [NSLayoutConstraint.init(item: toggleButton, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 128)]
        constraints += [NSLayoutConstraint.init(item: toggleButton, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 96)]
        NSLayoutConstraint.activate(constraints)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        addToggleButton(to: view)
    }
}


Embed Table Views

Here’s an app I started from the downloaded example file.

It is a restaurant check with a total, but I have to press the Order button to see the details in a table view on the next view controller.

That button is a waste of real estate, and it would be nice to see the details of the controller on this page. There’s a simple technique to do just that.
I’ll stop the app and go to the storyboard.

Delete the segue and the  Order button. Open the object library and find the container view.

This view embeds a view controller into the current view controller. Drag it out to where the Order button was,. You’ll find it has a view controller added to it though rather small. The size of the view controller is determined by the size of the container view.

Using auto layout, pin the container view 20 points on all sides. You’ll see the container view controller change size.

You can design a view within the container view controller. However for table views, I usually design them first and add them to the container view. Besides, I already have my table view controller.

Select the segue to the blank view controller and delete it. Delete the extra view controller. Control-drag from the view to the table view controller.

You’ll see one of the selections is Embed. Select that one.

Run the app and you’ll see the table on the main page.

Child controllers work just like any other view controller. You use delegates for transfer back to the parent, just like a separate controller.
Stop the app. I set up the table view controller with a delegate method for selecting an item from the table. The protocol and the call in the didSelect(tableview:AtIndexPath:) is already in place.
Head over to the view controller. Add the delegate to the view controller:

class ViewController: UIViewController, ItemsTableViewControllerDelegate {

Add the required method which will read the third value from the row entry, a detail about the food.

func didSelect(item: RDR1) {
        itemDetailLabel.text = item.detail
    }

Here’s a big difference with child view controllers: you don’t dismiss the controller, just send the data back through the delegate.
I need to assign the delegate property through the segue

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "table"{
            let vc = segue.destination as! ItemsTableViewController
            vc.delegate = self
        }
    }

Run this again, and try selecting the items. A description appears above the table.

This is one of many things you can do with container view, but I find it one of the most useful, especially when I want both a header for a table and detail items for it.

Layout iPad Landscape and Multitasking Apps

Note:You can find a video of this tip here on LinkedIn Learning

If there’s one thing in auto layout that drives me nuts it is laying out for landscape different than portrait on an iPad. Phones are easy since the class sizes are different for portrait and landscape. For all phones, if I have a compact height, I know I’m in landscape. But iPads are regular width, regular height, and so I have no clue to programmatically change the layout. Let’s look at one way to handle this.
Download the example file. Take a look at the storyboard.

I have a large number of buttons and an image. I want in landscape buttons to the side, while in portrait buttons below. I set this up with nested stack views, so by switching between vertical and horizontal axis I can set my layout appropriately.

Head to the ViewController code. What I’m going to do is look at the device rotation and decide what orientation I should be in. I need to generate orientation notifications. At the bottom of the code, in viewWillAppear, add a start method from UIDevice‘s singleton current:

UIDevice.current.beginGeneratingDeviceOrientationNotifications()

As s good developer I’ll shut this down when not on this page in viewWillDisappear.

UIDevice.current.endGeneratingDeviceOrientationNotifications()

Before I layout out the subview, I’ll change the orientation of the stack view and use a different image. I made methods for the different layouts. I’ll tell the system which layout I want in viewWillLayoutSubviews:
First I’ll get the orientation from UIDevice.

let orientation = UIDevice.current.orientation

The orientation is an enum. There’s two cases for landscape, and I’ll put both in an if statement, going to landscape if true and portrait if false:

if (orientation == .landscapeLeft)||(orientation == .landscapeRight) {
    landscapeLayout()
} else { //portrait
    portraitLayout()
}

This works for both iPhones and iPads. I got an iPad Pro 11″ and an iPhone XR warmed up and ready to go on the simulator. I’ll choose to run the iPad Pro in the simulator.and run the app.

Once it comes up , I’ll click simulator and drag the app into the iPhone XR then run it

Rotating them with Command-Right Arrow changes the layout.

But there’s a problem on an iPad. Add an extra pane from Safari. Multitasking lets me add another pane, and those don’t layout the way I’d like.

The key here is class sizes. With one exception, all the multitasking panes on an iPad act like portrait on the phone as compact width, regular height. The exception, the 2/3 pane in landscape, acts like an iPad with regular width, regular height. If you want this behavior, you don’t have to do anything, But in portrait I always want my portrait layout.
I’ll first check if this is a regular height view. In landscape, I’m on an iPad if that is true, so I’lll add some code there. I’ll then check for a compact width. I have a smaller multipane if that is true. If false,  we have landscape.  I’ll add this:

if isRegularHeight && isCompactWidth{
    portraitLayout()
} else {
    landscapeLayout()
}

Run this. You get the portrait layout in 2/3.

Change the pane size to 1/2 or 1/3, and it goes back to portrait.

 

Rotate  to landscape and it will stay portrait

.

Try with the phone and it works too. With a small bit of code,  you can get rotation and multipane layout for your iPad that reflects your iPhone layout.

The Whole Code

You can find the download of the completed project here. You can find a video of this tip here on LinkedIn Learning

//
//  ViewController.swift
//  Multipane rotation - Demonstrates landscape layout on iPad
//
//
//  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 {
//Outlets
    @IBOutlet weak var pizzaImageView: UIImageView!
    @IBOutlet weak var rootStackView: UIStackView!

// Computed properties for convenience
    var isRegularHeight:Bool{
        return view.traitCollection.verticalSizeClass == .regular
    }
    var isCompactWidth:Bool{
        return view.traitCollection.horizontalSizeClass == .compact
    }
    
// The layout methods
    func landscapeLayout(){
        rootStackView.axis = .horizontal
        pizzaImageView.image = UIImage(named:"Pizza Landscape")
    }
    
    func portraitLayout(){
        rootStackView.axis = .vertical
        pizzaImageView.image = UIImage(named: "Pizza Portrait")
    }

    override func viewWillLayoutSubviews() {
        let orientation = UIDevice.current.orientation
        if (orientation == .landscapeLeft)||(orientation == .landscapeRight) {
            if isRegularHeight && isCompactWidth{
                portraitLayout()
            } else {
                landscapeLayout()
            }
        } else { //portrait
            portraitLayout()
        }
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        UIDevice.current.beginGeneratingDeviceOrientationNotifications()
        
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        UIDevice.current.endGeneratingDeviceOrientationNotifications()
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }
    
}

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()
    }


}


Make Round Buttons and UIViews

In the good old days, Apple used a lot of rounded corner buttons. Many developers use several images to get the effect of round buttons. The CALayer of UIButton does have a feature for you to easily get rounded corners, and even circular buttons.
Download the code from GitHub and open the starter file. It doesn’t do much but look pretty. Head over to the storyboard and you’ll see it is a series of rectangular buttons in a stack view and one square button. I made outlets for the square button and for the stack view, but in this case not for the individual buttons.
Head over to the ViewController class. We are going to do all our work today in didLayoutSubviews. You’ll see why shortly. There is one method to make a rounded corner, cornerRadius. You set the radius in points to describe the rounded area. For example I can make the square button have some rounded edges like this:

squareButton.layer.cornerRadius = 15.0

Set the simulator to iPhone XR and run. You will find rounded corners on the order button

.

Stop the app.
Rounded corners work even better as a relative size to the smallest dimension, usually the height. I can change this line to

squareButton.layer.cornerRadius = squareButton.frame.height * 0.20

This is also why I’m doing this in viewDidLayoutSubviews. When using auto layout, I don’t know the height until here. Run and the button is rounded 20% of the height.


You can also consistently handle buttons in a stack view.
I’ll make a for loop to iterate through all the arranged sub views in the stack view.

for view in stackView.arrangedSubviews{
}

I’ll find the buttons in the stack view by casting the view to a UIButton.

if let button = view as? UIButton{
}

If they are buttons, I’ll round the corners 20%

button.layer.cornerRadius = button.frame.height * 0.20

Run this and you get rounded corners on your stack view buttons.

I like perfectly rounded ends, and you can get that by changing to 50% of the width. Stop the app and try this:

 
button.layer.cornerRadius = button.frame.height * 0.50

Run again and you get circular-end buttons.

If you apply this to a button that has the same width and height, like the square button, you’ll get a round button. Stop the app and try this:

squareButton.layer.cornerRadius = squareButton.frame.height * 0.50

Run again and you get a circle for a button.

There’s one more trick I want to show you. Stop the app, go to the storyboard and change the backgroundImage to Pizza Marg.

Run again and you get a square button

. cornerRadius Only applies to background colors and borders by default. For the background to be masked you need one more line

squareButton.layer.masksToBounds = true

Which sets everything to be masked. Now if you run, you get the circle button.

There’s one thing I want to point our about masksToBounds. It will kill any drop shows you use. Add the method buttonShadow I made
Run again. You see the stackview buttons have shadows but the round button does not. The mask of masksToBounds cuts off the shadow. In order to use both, you’ll need a better understanding of masks I’ll get to in a later tip.
Instead of messing with lots of assets, if you need rounded buttons you may want to explore CALayer’s corner radius as an option.

The Whole Code

You can also find a download of this code on GitHub

//
//  ViewController.swift
//  RoundButtons
//
//  A exercise file for iOS Development Tips Weekly
//  by Steven Lipton (C)2018, All rights reserved
//  For videos go to http://bit.ly/roundButtonsLinkedIn
//  For code go to http://bit.ly/AppPieGithub
//

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var stackView: UIStackView!
    @IBOutlet weak var squareButton: UIButton!
    @IBOutlet weak var imageView: UIImageView!
    
    
    override func viewDidLayoutSubviews() { //<-- Add code here
        squareButton.layer.cornerRadius = squareButton.frame.height * 0.50
        squareButton.layer.masksToBounds = true
        for view in stackView.arrangedSubviews{
            if let button = view as? UIButton{
                button.layer.cornerRadius = button.frame.height * 0.50
            }
        }
    }
    
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        imageShadow()
    }
    
    func imageShadow(){
        imageView.layer.shadowColor = UIColor(named: "#0000FF Match 1")?.cgColor
        imageView.layer.shadowOffset = CGSize(width: 5, height: 6)
        imageView.layer.shadowRadius = 4.0
        imageView.layer.shadowOpacity = 0.4
    }
    
    func buttonShadow(button:UIButton){
        button.layer.shadowColor = UIColor(named: "#0000FF Match 1")?.cgColor
        button.layer.shadowOffset = CGSize(width: 5, height: 5)
        button.layer.shadowRadius = 4.0
        button.layer.shadowOpacity = 0.4
    }

}

Use the Speech Synthesizer

It’s sometimes nice to add a little sound to your applications, and even better to add a voice. Outside Siri, you can use a cool and simple AVFoundation API to make iOS devices talk. Let’s learn how to use the Speech synthesizer.
Download the starter file. I’ve made an app for you that has a text field, a button and a segue to a table view we’ll discuss a little later.
Open the ViewController,swift file. Find the speak action. We’ll need to get a string from the text field. So add the following code to get the text if the field has a value:

if let speechText = speakingText.text{
}

You’ll need two objects: a synthesizer object and a speech utterance. But before you do, make sure you have imported AVFoundation.

import AVFoundation

I’ll add the synthesizer with the AVSpeechSynthesizer class

let synth = AVSpeechSynthesizer()
let speech = AVSpeechUtterance(string: speechText)
synth.speak(speech)

That’s it. Set your simulator to iPhone 8 Plus. Build and run the app. Type in some phrase, like
I could use a good pizza right now.
Tap the Speak! Button.

Your simulator speaks!!

Of course that’s not the only method you have at your disposal. You can easily change the voice as well. Voices are a localization in pronunciation. Some languages will have more than one available pronunciation. I set up the app to list all the voices and their language combinations on a table view and then the user can select the voice and accent.

You’ll use the AVSpeechSynthesisVoice class for this. There is a class method that returns an array of voices. I’ll use this for my model for the table

let voices = [AVSpeechSynthesisVoice.speechVoices()] //list of voices

Head down the code to the tableview.dequeue method. Remove what I have their now. All the voices have, names, Identifiers and languages. Names are only indication if they are male or female voices until you hear them. You use the name property. Since these properties are strings. we can assign the name directly to the text label and the language to the detail text label of the table cell.

cell.textLabel?.text = voice.name
cell.detailTextLabel?.text = voice.language

When a language is selected I’ll send that voice back from the table and dismiss the table. I set up the delegation for you. In didselect, uncomment out the two lines. If you don’t understand what I’m doing here take a look at the delegates and data sources course in the library.
Back in ViewController, you’ll see I have the delegate method defined to transfer the voice to a variable. All you need to do is assign the voice with the action to the variable.

Speech.voice = speechVoice

Now run. Tap voice and then pick another voice. Try the voice.

Play around with this. Of course,  voices are more for localization than entertainment. There’s more to the API, but with these basics you can make your app talk.

The Whole Code

ViewController.swift

//
//  ViewController.swift
//  SpeechSynthesizer
//
//  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
import AVFoundation

class ViewController: UIViewController, VoiceTableViewControllerDelegate {

    @IBOutlet weak var speakingText: UITextField!
    var speechVoice = AVSpeechSynthesisVoice()

    @IBAction func speak(_ sender: UIButton) { //<-- Add code here
        if let speechText = speakingText.text{
           let synth = AVSpeechSynthesizer()
            let speech = AVSpeechUtterance(string: speechText)
            speech.voice = speechVoice
            synth.speak(speech)
        }
    }

    //delegate function
    func didSelectVoice(voice: AVSpeechSynthesisVoice) {
        speechVoice = voice
        title = voice.name
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        title = "Default Voice"
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "voices"{
           let  vc = segue.destination as! VoiceTableViewController
            vc.delegate = self
        }
    }

}

VoiceTableViewController.swift

//
//  VoiceTableViewController.swift
//  SpeechSynthesizer
//
//  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
//
//  A table view controller for selecting a language
//

import UIKit
import AVFoundation

protocol VoiceTableViewControllerDelegate{
    func didSelectVoice(voice:AVSpeechSynthesisVoice)
}

class VoiceTableViewController:UITableViewController {

    let voices = AVSpeechSynthesisVoice.speechVoices() //list of voices
    var delegate:VoiceTableViewControllerDelegate! = nil

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return voices.count
    }
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        let row = indexPath.row
        let voice = voices[row]
        cell.textLabel?.text = voice.name
        cell.detailTextLabel?.text = voice.language
        return cell
    }
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let row = indexPath.row
        delegate.didSelectVoice(voice: voices[row])
        navigationController?.popViewController(animated: true)
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

}

The Settings Bundle

You’ll often want default settings that users can change. iOS provides the settings bundle for storing this information. Let’s look at how to use the settings bundle.

Download the starter example file. I created a project which eventually will display the user’s default pizza. We’ll finish the code to add that pizza.

To add a settings menu Go to File>New>File on the menu. In the filter type sett. Under resources you’ll see the settings bundle. You’d Select it and click Next, then Save it into the project group. I’ve already set one up for you, so you can hit Cancel.

Find the settings bundle, click the arrow to find the root.plist. The third item  is Preference Items. Open that.

You’ll find several items already here. There are several types of controls here. Item 3 is sliders. I find them difficult to use in settings. Delete the slider by clicking to the left of the arrow to select and hit Delete on your keyboard. Right-click on Item 1. A drop down asks you for what control here and select multi value.

Expand out all the Items. Each has different values. The Type is the type of control, the Title is the title showing in the settings app, and Identifier is your key for accessing this value. I set up the text field and the toggle switch. Let set up together the Multi-value.

For title make it Size. For Identifier, make it size_preference. I’m going to leave the default value blank, but click on default value to get the + icon. Click the icon and you’ll see two more attributes not here yet. Titles and Values. You need both for a multi value. Click on Titles first. In title, hit the plus four times. For item names add Personal, Small, Medium and Large. Go back to default value, and hit the plus again to add the values, which pair with the titles. Add one value and change the type to number. Add three more items.

You’ve now added the multi value. Head to ViewController.swift I’ve added some code here for you, but we’ll now complete it.

Userdefaults.standard is a singleton for the user defaults settings. I’ll make a shorter identifier for UserDefaults.standard called defaults:

let defaults = UserDefaults.standard

User defaults has several methods with the key as the identifier from the Root.plist. These methods, based on type retrieves the data from the settings page. The switch uses a Bool, and the text uses an optional string.

Let’s add the MutiValue. We made the value an integer, so we can use integer here, then add it to the string.

let size = defaults.integer(forKey: "size_preference")
text += String(format: "%i\" ", size)

Head down to viewDidLoad.

We need to tell the app we have a settings bundle. Add the following code to register the settings bundle with a dictionary with a string key.

UserDefaults.standard.register(defaults: [String:Any]())

While the code we have will work, if a user changes default settings while using the app, that will not show up in the app. We need a key-value observer to tell us when there is a change, and to update our favorite pizza.

NotificationCenter.default.addObserver(self,selector: #selector(defaultsChanged),name: UserDefaults.didChangeNotification,object: nil)

For the selector, I set the defaultsChanged method to an IBAction, so I can use that.

I have a call here for defaultsChanged already. So I’ll set the simulator to iPhone 8 plus and run. We get a boring pizza. Head over to settings and scroll to the bottom to find ad select the favorite pizza settings. I’ll add some topping, make a pan pizza and make the pizza medium. Go back to the app and you find your pizza there.

That’s all you need to get settings into your app for defaults and other important user preferences that don’t change often. For more on the attributes, check out the pdf chart I placed in the assets folder of the exercise file. There’s a lot you can do.

Customizing Tab Bar Controller Icons.

One of the more perplexing parts of using tab bar controllers is customizing tabs. If you understand some of the properties of an image, You can do some major customization.
Download the example file. If you open the main storyboard you’ll see three view controllers. You may see Question marks in the image like this:

2018-09-26_06-37-40

If you do, Click on Assets to make sure the assets are there and then head back to the storyboard. Zoom out to see all three.

2018-09-26_06-47-46.jpeg

I’ll make a new tab bar controller. Marquee select all three controllers, and then from the menu bar select Editor>Embed> Tab Bar controller. All three are now in the tab bar controller, which I’ll spread out a little.

2018-09-26_06-50-14.jpeg

Zoom to the bottom of the tab bar controller and you’ll see the three icons I already added to this project. Two have writing in them and the third is a solid square.

2018-09-26_06-51-36

In the properties for that tab bar controller, check Is Initial view controller. Set your simulator to iPhone X and run.

This app is a reference guide to the three ways you can use Icons: square, circle or background. While running, you’ll see that the square and circle are solid when running, and the background is always solid.

2018-09-26_06-56-30.gif

That text is too small to see well, So I made a larger version on each tab as an image view. Stop the app.

Images in the tab bar are by default template images. A template image is a monochrome image using the tint color. Any solid areas are rendered solid while transparent areas are rendered the background. Only the alpha channel of an image is used for the color. Go to the SquareViewController. The image in the image view is an image with transparent ares for the text. In viewDidLoad, I can change the rendering mode of the image by changing the rendering mode property of the image like this:

imageView.image = imageView.image?.withRenderingMode(.alwaysTemplate)

Run this and the yellow square in the app is now the tint color.

2018-09-26_07-04-57
Stop the app. You can use rendering mode to change to a tab bar image as an .original rendering mode. Tab bar items are stored within the view controller in the tabBarItem property, so under the line we just typed, add this:

tabBarItem.image = tabBarItem.image?.withRenderingMode(.alwaysOriginal)

Nothing seems to happen, but Click the circle tab and you’ll see a yellow square.

2018-09-26_07-09-19.jpeg

Stop the app and go to the storyboard. Select the tab for the circle and you’ll see the three attributes for the images to the tab bar.

2018-09-26_07-11-45.jpeg

The Image listed under the Bar Item is Circle Template. Under that is Landscape, a compact version of the tab bar for landscape on compact height devices, where I put Circle Compact Template for the image. These set the image when the tab is not selected.  Under Tab Bar Item is the one I want to concentrate on:Selected Image. This is an image that’s meant to display when the bar item is selected.  Circle is an original rendered as a template, so we get a solid circle.

This is true of our Square too.

2018-09-26_07-20-31.jpeg

When we ran the app the square was selected as the initial controller, but it was still in a template rendering mode. Change the code in the square to this:

 tabBarItem.selectedImage = tabBarItem.selectedImage?.withRenderingMode(.alwaysOriginal)

Copy this line and paste it into the code for the other two controllers’ viewDidLoad. Run again.

2018-09-26_07-27-52.gif

This time the square is yellow with writing, click the circle and it is red. The background show s a gradient. The non selected tabs are the tint color.

This was a simple example. You can use this trick in a variety of ways, from having a single selection image for all your tabs to having color icon tabs for all your images. There’s plenty of combinations that give you more flexibility on how to make tab icons.

The Whole Code

You can find this code at GitHub here. You can also find a video demo of this tip on LinkedIn learning.

SquareViewController.swift

//
//  SquareViewController.swift
//
//  A Demo 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 SquareViewController: UIViewController {

    @IBOutlet weak var imageView: UIImageView!
    override func viewDidLoad() {
        super.viewDidLoad()
        imageView.image = imageView.image?.withRenderingMode(.alwaysTemplate)
        tabBarItem.selectedImage = tabBarItem.selectedImage?.withRenderingMode(.alwaysOriginal)
        // Do any additional setup after loading the view.
    }

}

CircleViewController.swift

//
//  CircleViewController.swift
//
//  A Demo 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 CircleViewController: UIViewController {

    @IBOutlet weak var imageView: UIImageView!
    override func viewDidLoad() {
        super.viewDidLoad()

         tabBarItem.selectedImage = tabBarItem.selectedImage?.withRenderingMode(.alwaysOriginal)
    }

}

BackgroundViewController.swift

 

//
//  BackgroundViewController.swift
//
//  A Demo 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 BackgroundViewController: UIViewController {

    @IBOutlet weak var imageView: UIImageView!
    override func viewDidLoad() {
        super.viewDidLoad()

        tabBarItem.selectedImage = tabBarItem.selectedImage?.withRenderingMode(.alwaysOriginal)
    }

}

Add Provisional Permissions for Notifications

If you’ve worked with notifications before, you’ve probably dealt with user permissions. IN iOS 12, there’s a new type of user notification permission to handle a situation that happens often, which is super easy to add to already existent code.

Download the exercise file and you’ll find a project I adapted from chapter three of my Course Learning iOS Notifications . I tidied up the storyboard a bit and converted the file to iOS 12. Open up Xcode 10 with the project.

We need a clean simulator to show this correctly. Open up the simulator and choose an iPhone 8 plus. Click Hardware and Erase Content and settings..

and select Erase the content.

Head to the view controller and viewDidLoad . I have the notification permissions set here .

UNUserNotificationCenter.current().requestAuthorization(options: [.alert,.badge,.sound]) { (granted, error) in
            self.isGrantedNotificationAccess = granted
            if !granted {
                //add alert to complain to user
            }
        }

I’ll run this with an iPhone 8 simulator, which is just a lot easer to navigate in a  I get the permissions message.

This is the current way to set up user permissions for notifications. Click Allow.

Click the Schedule  a Pizza and Make a Pizza button  a few times, which will send some notifications to your simulator. Wait a few seconds and You’ll see a notification.

Add a few more notifications. Press Command-L to lock the phone. The notification appears.

Press Home or  Commmand-Shift-H to go back into the app. Swipe down from the top to go to the notification center. You’ll see your notifications.

You’ll also the new iOS 12 feature of stacking when too many of the same type of notification appears, they stack to clean up your notifications. to see each nitrification, click the top one.

Swipe a notification to the one right again and you’ll have three choices.

View  view the notification in full and Clear clears it. Tap Manage.

The user can set notification preferences from any notification in iOS12. They can turn them off or change other settings. They also have one new mode: Deliver Quietly. This will display the notification only in the notification center. You won’t see the notification on a home screen or in a banner to interrupt you.

Stop the app, then erase the simulator’s content and settings. All of the above was to show you how notifications works under normal circumstances. The new permission state for notifications is the provisional state. This is a kind of sampler of notifications so users can decide if notifications are worth their time. There’s no permission question. Notifications default to deliver quietly instead of prominently. To add this functionality to your app, you add .provisional to your list of options for requesting authorizations. 

NUserNotificationCenter.current().requestAuthorization(options: [.alert,.badge,.sound,.provisional]){

Run this. Schedule a  few pizza and make a few  pizzas. You can wait a while either on lock or in the app, but you will see no notifications any where. Press home and the notifications are still not there. Open the notification center, and you’ll find your notifications.

Open the stack  and go to the first one, on the bottom of the stack.

On this notification the user decides if they want to turn off the notification.   The user can also tap keep to change the delivery method.

Going back to Deliver Prominently gives you banner notifications.

The Provisional Permissions option lets your user sample your notifications without a pesky permission question. With a single change to code you get a new functional you will want to use if your app uses notifications.

Changes to Xcode 10 Storyboards.

There’s been some great changes to playgrounds in Xcode 10 that will make your code even easier to prototype than ever before. Let’s take a look at some of the changes. 

In Xcode 10 open a new single view playground, and save it as Xcode10Playground. If you are familiar with the Xcode 9 playground single view template, you’ll notice a difference. Instead of setting up my view in viewDidLoad, this sets up  my view in loadView. This will prevent a bug that happens in iPad playgrounds and sometime Xcode where  the superview is still nil in viewDidLoad. 

When working with view controllers and setting up your user interface, use this pattern of adding all your views to a superview, then assigning the superview to the view property of the view controller. Also make sure you set the frame of the superview, which you can get from the nativeBounds property of UIScreen.  nativeBounds will be in portrait.  Since I use iPad playgrounds most of the time, this code in my view controller sets the frame correctly for iPad landscape, and adds a label, which I’ll have to layout and set properties elsewhere. Strangely it doesn’t always work, But I’m still trying to find a consistent  solution. This works more often than others.

 

Xcode Dark Mode

A big feature change to macOS is dark mode, and Xcode supports this. I can show you the really cool features a little easier in dark mode.  So I’ll go to Editor, select Theme and select Presentation Dark. 

2018-09-12_07-13-34

If you look to the right of the line numbers, you’ll see some lines.

2018-09-12_07-15-03

That’s for code folding, which is a new feature to Xcode Playgrounds. If you hover over the line you get a set of arrows giving you an idea what you are about to fold. I can fold 

Loadview and MyViewController by clicking on the fold line.

2018-09-12_07-15-58.jpeg I’ll click on the arrow and they unfold again.  If you don’t have code folding or want to turn it off. There is a setting in Xcode preferences. Go to text editing and check on or off Code folding ribbon according to your preference.

2018-09-12_07-38-04.jpeg

Selective Run Mode

You’ll also notice that circle with a play button next to playground page on the line numbers. The biggest change is a selective run mode.

2018-09-12_07-16-24.jpeg

I can still run using the run on the bottom toolbar, but you can also run using this button. I can click on the new play button, open the live view and my playground runs. 

I’ll stop the playground and close the live view. Notice what happens when I drag the play button up and down the code. It skips over the class.

This will only let you run code that makes sense. Nested statements or classes, essentially anything you can fold, will not run. 

Fold up the view controller and give your self some space above it to play with this.  Add a variable a with a random number using my new favorite API. iOS 12 has a simple to use random function, which you use as as class method of the type your are working with. 

var a = Int.random(in: 1...10)</pre>
Then make a simple for loop from one to five. I'll skip a variable for it with a underscore. 
for _ in 1...5{

}

There a beautiful new random number generator in iOS 12. random(in:) is a class function you give a range to, working with several types of numbers. I’ll add a random number of 1 to 10 to the sum of a:

a += Int.random(in: 1...10)

Then print this

print(a)

I’ll then move my mouse over the line numbers, and you can see that assignment, the print, and the end of the for loop can run. I’ll run the assignment and I get a random number on the right.

2018-09-12_07-29-05
  The line number is no longer highlighted. 

 Run at the end of the loop and you get five iterations of the summation, which you can see if I open up a viewpoint.

2018-09-12_07-34-02.jpegI also have more lines not highlighted, telling me I ran those already. Change the assignment line by adding 1 to it, and the run resets. with a blue number

2018-09-12_07-34-48.jpeg

I’ll run to the print this time. I get my result  in the panel and the code runs. Note the random number is frozen so you can step through this code.  That’s different than pressing the run button on the bottom which changes your random numbers. If you are testing with data that might change every execution, selective execution make life much easier. 

While it does not run within a class, this selective run can help you prototype your code, letting you change and run as you step through an algorithm. Write the code like I did here and then make it a function or class. You can trace and build your code easily when you need to see every statement run.