It is not clear from the Platinum Challenge requirements how the color panel should be presented and have a look. This matter of a guess led me to three options:
- An instance of UIMenuController;
- A subview with buttons added to the ‘DrawView’;
- A separate UIView with buttons and with its own UIViewController.
My choice was the third option because:
- UIMenuController is a context menu aimed to copy past etc. the selected item. Using this menu for control is not appropriate to its purpose. And this is a too easy solution to be named ‘Platinum’.
- The advantage of adding and removing a subview is that just one UIViewController does all the job. The drawbacks are over complicated storyboard and breaking the MVC rule that each main view is managed by a separate controller.
- Well, here is all clear UISwipeGestureRecognizer trigger UISegue to the UIViewController managing color buttons logic. Nice.
Implementation begins with changing the name of the canvas view controller from ‘ViewController’ to ‘DrawViewController’ both in the source code and in the storyboard plus creating new ‘ColorPanelViewController’ here and there too. In the storyboard, I added 8 UIButtons to this new controller’s view: ‘Blue’, ‘Yellow’, ‘Orange’, ‘Purple’, Green’, ‘Red’, ‘Cyan’ and ‘Default’. Their tag property values were changed in the attribute inspector from 1 to 8 correspondingly. All these buttons were connected to an action method buttonPressed(: ) where the value of the tag property sets the color property of the instance of the ‘DrawView’.
Therefore full implementation of the new ColorPanelViewController is this:
class ColorPanelViewController: UIViewController {
var theView: DrawView!
@IBAction func buttonPressed(_ sender: UIButton) {
let tag = sender.tag
switch tag {
case 1:
theView.chosenColor = UIColor.blue
case 2:
theView.chosenColor = UIColor.yellow
case 3:
theView.chosenColor = UIColor.orange
case 4:
theView.chosenColor = UIColor.purple
case 5:
theView.chosenColor = UIColor.green
case 6:
theView.chosenColor = UIColor.red
case 7:
theView.chosenColor = UIColor.cyan
default:
theView.chosenColor = nil
}
dismiss(animated: true, completion: nil)
}
}
As you may have noticed, the instance of ‘DrawView’ has a new color property ‘chosenColor’. This is an optional UIColor? storing the user’s color preference. The default is nil letting the color to be set the old way. Here you are the changes in the ‘DrawView’ implementation, post here only the changes relevant to this topic:
class DrawView: UIView, UIGestureRecognizerDelegate {
...
var chosenColor: UIColor?
var swipeRecognizer: UISwipeGestureRecognizer!
...
required init?(coder aDecoder: NSCoder) {
...
addGestureRecognizer(longPressRecognizer)
swipeRecognizer = UISwipeGestureRecognizer(target: nil, action: nil)
swipeRecognizer.numberOfTouchesRequired = 3
swipeRecognizer.direction = .up
swipeRecognizer.delaysTouchesBegan = true
addGestureRecognizer(swipeRecognizer)
...
moveRecognizer.require(toFail: swipeRecognizer)
...
}
...
override func draw(_ rect: CGRect) {
...
for (_, line) in currentLines {
if chosenColor == nil {
currentLineColor = UIColor(hue: line.angleSin, saturation: 1, brightness: 1, alpha: 1) //Solution to the Chapter 18 Silver Challenge
} else {
currentLineColor = chosenColor
}
currentLineColor.setStroke()
stroke(line)
}
...
}
...
}
The second change in the ‘DrawView’ is creating a UISwipeGestureRecognizer and adding it to this view. The target and the action parameters of the recognizer’s initializer are ‘nil’ because they will be provided in the view controller. It is very important to put UISwipeGestureRecognizer before the ‘moveRecognizer’ so that the latter can check whether a swipe was detected. The instance of the UISwipeGestureRecognizer is placed in a property ‘swipeRecognizer’ reference to which will be in the ‘DrawViewController’, post here only the changes relevant to this topic:
class DrawViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let view = self.view as! DrawView
view.swipeRecognizer.addTarget(self, action: #selector(swipe))
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowColorPanel" {
let controller = segue.destination as! ColorPanelViewController
controller.theView = self.view as! DrawView
}
}
@objc func swipe() {
print("swipe")
performSegue(withIdentifier: "ShowColorPanel", sender: self.view)
}
}
In the ‘viewDidLoad()’ method we add target-action pair to the ‘swipeRecognizer’. The action ‘swipe()’ calls performSegue(withIdentifier:sender:) to show the color panel view. This nesting is done to follow OO encapsulation rule that if in the future requirements change what this swipe must do, we will change logic just in one place. In the ‘prepare(for:sender:)’ method we inject dependency of the ‘DrawView’ instance into ‘ColorPanelViewController’ instance to keep the user’s choice of the color.