SwiftでcolorPickerを作る


## 何を作りたいか
色を直感的に選択できるアレです。

git → https://github.com/ha1fha1f/colorPicker

![スクリーンショット_2015_11_07_22_56.png](https://qiita-image-store.s3.amazonaws.com/0/77778/a6bdaec5-c557-a743-2a07-3350fbc71985.png "スクリーンショット_2015_11_07_22_56.png")

色の選択はHSVを元にして、HとSを主な軸として描きました。一番上の段は、V値の低い黒や灰を選択できないので、無理やりたした感じ・・・

軸を分けると直感的に選べない＆1タップで色を選べないと思い、二次元で表現することにしました。
真ん中をs=1, v=1にして、左へはsの減少、右へはvの減少、みたいな実装も見かけますが、それだと同じような色が右にも左にもあってわかりにくいと思ったので。

ブロックにしたのは、同じ色を選びたい時に選べるからです。


## UIPopoverPresentationController
popOverとして表示させることのできるビューです。iPhoneでも使えるようになりました。(iOS 8.0以降)

ColorPickerViewController()は表示させたいビューのインスタンスを生成しています。そちらは普通のUIViewControllerで、特別な操作は何も必要ありません。

参考 → http://www.minimalab.com/blog/2015/01/30/iphone-uipopovercontroller/


### コード全体

```swift:ViewController.swift
import UIKit

class ViewController: UIViewController, UIPopoverPresentationControllerDelegate {
    
    var setColorButton: UIButton! = nil

    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.view.backgroundColor = UIColor.lightGrayColor()
        
        if setColorButton == nil {
            setColorButton = UIButton(frame: CGRect(origin: CGPointZero, size: CGSizeMake(50, 50)))
            setColorButton.layer.position = CGPointMake(self.view.bounds.width - 50, self.view.bounds.height - 50)
            setColorButton.backgroundColor = UIColor.redColor()
            setColorButton.addTarget(self, action: "clicked:", forControlEvents: .TouchUpInside)
            self.view.addSubview(setColorButton)
        }
    }
    
    func clicked(sender: UIButton!) {
        let controller = ColorPickerViewController()
        self.presentPopver(controller, sourceView: sender)
    }
    
    func presentPopver(viewController: UIViewController!, sourceView: UIView!) {
        viewController.modalPresentationStyle = UIModalPresentationStyle.Popover
        viewController.preferredContentSize = CGSizeMake(300,400)

        let popoverController = viewController.popoverPresentationController
        popoverController?.delegate = self
        // 出す向き(DownはsourceViewの上)
        popoverController?.permittedArrowDirections = UIPopoverArrowDirection.Down
        // どこから出た感じにするか
        popoverController?.sourceView = sourceView
        popoverController?.sourceRect = sourceView.bounds
        
        self.presentViewController(viewController, animated: true, completion: nil)
    }
    
    func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
        return UIModalPresentationStyle.None
    }   
}
```

```swift:ColorPicerViewController.swift

class ColorPickerViewController: UIViewController {
	override func viewDidLoad() {
	    self.view.backgroundColor = UIColor.redColor()
	    print(self.preferredContentSize)
	}
}

```

### 説明

#### iPhoneでもポップアップ表示

```swift
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
    return UIModalPresentationStyle.None
}
```

###

これで、iPhoneでもモーダル表示でなく、popup表示を行うことができます。
参考 → https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIAdaptivePresentationControllerDelegate_protocol/index.html#//apple_ref/occ/intfm/UIAdaptivePresentationControllerDelegate/adaptivePresentationStyleForPresentationController:



#### present

```swift
viewController.modalPresentationStyle = UIModalPresentationStyle.Popover
viewController.preferredContentSize = CGSizeMake(300,400)

let popoverController = viewController.popoverPresentationController
popoverController?.delegate = self

// 出す向き(DownはsourceViewの上)
popoverController?.permittedArrowDirections = UIPopoverArrowDirection.Down
// どこから出た感じにするか
popoverController?.sourceView = sourceView
popoverController?.sourceRect = sourceView.bounds

self.presentViewController(viewController, animated: true, completion: nil)
```

self.presentViewController()でpopupを表示させるのはAlertとかとも同じです。

.sourceViewは吹き出しの矢印がとこから出るかを示します。

.preferredContentSizeで、popover画面のサイズを指定します。遷移後の画面からも、この名前で受け取ることができます。

#### delegate
今回は不使用ですが、参考 → https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIPopoverPresentationControllerDelegate_protocol/index.html#//apple_ref/occ/intfm/UIPopoverPresentationControllerDelegate/popoverPresentationControllerDidDismissPopover:

これらのdelegateは、dismissViewController()で終了された場合は呼ばれません。popoverViewの画面外をタップすることで終了された場合のみよばれます。


## 色を描く

お家芸、CoreGraphics
UIViewをおいてもいいですが、無駄なので。

```swift
class ColorsView: UIView {
    // 細かさの設定
    var xCount = 15
    var yCount = 20
    
    var blockSize: CGSize! = nil
    var size: CGSize! = nil
    
    func setUp() {
        self.size = self.bounds.size
    }
    
    func colorFromPos(posH: Int, posS: Int) -> UIColor {
        // 白〜黒のやつ
        if posH == 0 {
            return UIColor(hue: 0, saturation: 0, brightness: 1.0-CGFloat(posS)/CGFloat(xCount-1), alpha: 1.0)
        } else {
            return UIColor(hue: CGFloat(posH-1)/CGFloat(yCount-1), saturation: CGFloat(posS+1)/CGFloat(xCount), brightness: 1.0, alpha: 1.0)
        }
    }
    
    func colorFromPoint(point: CGPoint) -> UIColor {
        let posX = Int(point.x * CGFloat(xCount) / size.width)
        let posY = Int(point.y * CGFloat(yCount) / size.height)
        return colorFromPos(posY, posS: posX)
    }
    
    
    override func drawRect(rect: CGRect) {
        let context = UIGraphicsGetCurrentContext()
        
        let blockSize = CGSizeMake(size.width/CGFloat(xCount), size.height/CGFloat(yCount))
        
        for i in 0...yCount {
            for j in 0...xCount {
                let color = colorFromPos(i, posS: j)
                color.setFill()
                let blockRect = CGRect(
                    origin: CGPointMake(blockSize.width*CGFloat(j), blockSize.height*CGFloat(i)),
                    size: blockSize
                )
                CGContextFillRect(context, blockRect)
            }
        }
    }
}
```

はじめUIViewで作ったのもあり、変な実装になってしまいましたが・・・

## colorPicker

```swift:ViewController.swift
extension ViewController: ColorPickerViewDelegate {
    func onColorChanged(newColor: UIColor) {
        self.setColorButton.backgroundColor = newColor
    }
}
```

受け取るためのdelegateをimplementして、受け取る。

```swift:ColorPickerViewController.swift
protocol ColorPickerViewDelegate {
    func onColorChanged(newColor: UIColor)
}

class ColorPickerViewController: UIViewController {
    var delegate: ColorPickerViewDelegate! = nil
    
    var colorsView: ColorsView! = nil
    
    var currentColor: UIColor = UIColor.whiteColor()
    
    override func viewDidLoad() {
        self.view.backgroundColor = UIColor.redColor()
        
        self.view.layer.cornerRadius = 1.0
        
        if colorsView == nil {
            colorsView = ColorsView(frame: CGRect(origin: CGPoint.zero, size: self.preferredContentSize))
            colorsView.setUp()
            self.view.addSubview(colorsView)
        }
    }
    
    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
        let touch = touches.first?.locationInView(self.view)
        updateColor(colorsView.colorFromPoint(touch!))
    }
    
    override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
        let touch = touches.first?.locationInView(self.view)
        updateColor(colorsView.colorFromPoint(touch!))
    }
    
    override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
        let touch = touches.first?.locationInView(self.view)
        updateColor(colorsView.colorFromPoint(touch!))
        
        closeView()
    }
    
    func closeView() {
        self.dismissViewControllerAnimated(false, completion: nil)
    }
    
    func updateColor(color: UIColor) {
        self.currentColor = color
        delegate?.onColorChanged(self.currentColor)
    }
}
```

一番左の列は真っ白である必要が無い（一番左上が真っ白）のでこのような実装にしました。