Day 84 - 100 Days of Swift

4 minute read

Project 25 (part 2)

Day 84 is the second part of the twenty-fifth project. You review the things you learned about Multipeer Connectivity and he gives you a few challenges to extend the app. He challenges you to show an alert when a user has disconnected from the network. He challenges you to send text messages through the network. And he challenges you to add a button which shows an alert listing all the users in the session.

For the first challenge I first pulled the code for presenting a basic alert out into its own function, since we’re now using this in multiple places:

1
2
3
4
5
6
7
8
9
10
11
private func presentAlert(title: String? = nil,
                          message: String? = nil) {
    let ac = UIAlertController(title: title,
                               message: message,
                               preferredStyle: .alert)
    ac.addAction(UIAlertAction(title: "OK",
                               style: .default))
    DispatchQueue.main.async {
        self.present(ac, animated: true)
    }
}

Then I updated the current alert to use this function and added another call in the .notConnected section of the switch statement in peer:didChange:

1
2
3
4
5
6
7
8
9
// In the catch block of sendImage
let title = "Error Sending Image"
let message = error.localizedDescription
presentAlert(title: title, message: message)

// In the .nonConnected case of session(_:peer:didChange
print("Not Connected: \(peerID.displayName)")
let title = "\(peerID.displayName) Disconnected"
presentAlert(title: title)

This seems to have a bit of delay to it, but it eventually notifies the user when someone has left the network.

The second one required a little more overhead. The first thing I did was add a function for sending some message. It is almost exactly the same as sending an image:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private func sendMessage(_ message: String?) {
    guard let mcSession = mcSession else { return }
    guard mcSession.connectedPeers.count > 0 else { return }
    guard let message = message else { return }
    guard let messageData = message.data(using: .utf8) else { return }
    do {
        try mcSession.send(messageData,
                            toPeers: mcSession.connectedPeers,
                            with: .reliable)
    } catch {
        let title = "Error Sending Message"
        let message = error.localizedDescription
        presentAlert(title: title, message: message)
    }
}

It unwraps all the optionals and then tries to send the message data and presents an alert if something goes wrong.

Then I needed a way for the user to input that message. For simplicity’s sake I just made an alert controller with a textfield and added a UIBarButtonItem to present it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@objc func composeMessage() {
    let ac = UIAlertController(title: "Send a message",
                               message: "Say whatever you want. Try to keep it nice though.",
                               preferredStyle: .alert)
    var textField: UITextField!
    ac.addTextField { textfield in
        textField = textfield
        textField.placeholder = "Your message"
    }
    let send = UIAlertAction(title: "Send",
                             style: .default) { _ in
                                self.sendMessage(textField.text)
    }
    ac.addAction(send)
    let cancel = UIAlertAction(title: "Cancel",
                                     style: .cancel)
    ac.addAction(cancel)
    present(ac, animated: true)
}

// In viewDidLoad
let cameraButton = UIBarButtonItem(barButtonSystemItem: .camera,
                                   target: self,
                                   action: #selector(importPicture))

let messageButton = UIBarButtonItem(barButtonSystemItem: .compose,
                                    target: self,
                                    action: #selector(composeMessage))
navigationItem.rightBarButtonItems = [cameraButton, messageButton]

Finally, I just needed a way for the receiving user to see it. Again, I just presented an alert, because that seemed like the simplest way to go about it:

1
2
3
4
5
// In session(_:didRecieve:fromPeer)
else if let message = String(data: data, encoding: .utf8) {
    let title = "\(peerID.displayName) Says:"
    self?.presentAlert(title: title, message: message)
}

And with that the users can send messages back and forth (albeit in a somewhat cumbersome way):

For the third challenge I added yet another UIBarButtonItem that calls a function which presents yet another alert with the necessary info in it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@objc func showInfo() {
    guard let mcSession = mcSession else { return }
    let title = "Session Info"
    var message = "Connected Users:\n"
    message += mcSession.connectedPeers.map { $0.displayName }.joined(separator: "\n")
    presentAlert(title: title, message: message)
}

// In viewDidLoad
let addButton = UIBarButtonItem(barButtonSystemItem: .add,
                                target: self,
                                action: #selector(showConnectionPrompt))
let infoButton = UIBarButtonItem(title: "Info", style: .plain, target: self, action: #selector(showInfo))
navigationItem.leftBarButtonItems = [addButton, infoButton]

mcSession = MCSession(peer: peerID,
                      securityIdentity: nil,
                      encryptionPreference: .required)

Which leads to something that looks like this:

You can find my version of this project at the end of day 84 on Github here.