アイコンが縦にスクロールするSegmentedControlの機能を持つ自作UI

必要なので、以下のGIFのようなものを作ってみました。かなりやっつけなのでいろんな問題を含んでいると思います。コード長が長いのは、主にアイコンの位置調整とサイズ調整によるものと思われます。

本当はスクロールし続ければ2つ3つ先のアイコンも選択できるようにしようかと思いましたが、面倒になってやめてしまいました。なので1個ずつスワイプする形になります。
![SwipeRadioControl.gif](https://i.gyazo.com/7859dcef4a0d8850ff447f1e843a45d2.gif)

使い方は単純で、アイコンのUIImageとその識別子StringをArrayにして、後はViewの位置やアイコンのサイズなどを指定すればよいです。
selectedSpaceというのは、選択しているアイコンとその下のアイコンとの隙間の長さで、
unselectedSpaceというのは、選択していないアイコン同士の隙間の長さです。これらを0にすると、画像同士が縦にぴったりくっついてスクロールします。

識別子は、Delegateによって func didSelectIcon(iconID: String) がコールされるので、その時に使われます。

```.swift
let swipeRadioButtonView = SwipeRadioButtonView(
  origin: CGPointMake(100, 100),
  icons: [
    Icon(image: UIImage(named: "LV"), id: "LV"),
    Icon(image: UIImage(named: "HP"), id: "HP"),
    Icon(image: UIImage(named: "TP"), id: "TP"),
    Icon(image: UIImage(named: "AK"), id: "AK"),
    Icon(image: UIImage(named: "DF"), id: "DF"),
    Icon(image: UIImage(named: "SP"), id: "SP")
  ],
  bigIconSize:      CGSizeMake(100, 100),
  smallIconSize:    CGSizeMake(50, 50),
  selectedSpace:    20.0,
  unselectedSpace:  10.0
)

swipeRadioButtonView.delegate = self

```

```Swift
extension ViewController : SwipeRadioButtonViewDelegate {
  func didSelectIcon(iconID: String) {
    print("\(iconID) selected!")
  }
}
```

以下がコードですが、無駄にSwift2.0で書かれているので、Xcode6以下で実行するとコンパイルエラーが出ると思います。

```SwipeRadioButtonView.swift
import UIKit

protocol Scalable {
  func scaleWithSize(size: CGSize)
}

class Icon : UIImageView, Scalable {
  
  private let id: String
  
  required init?(coder aDecoder: NSCoder) { fatalError() }
  
  init(image: UIImage?, id: String) {
    self.id = id
    super.init(image: image)
    contentMode = .ScaleAspectFit
  }
  
  func scaleWithSize(size: CGSize) {
    frame.size = size
  }
}

class IconRotateQueue {
  
  private var queue: [Icon]
  
  func up() {
    queue.append(queue.first!)
    queue.removeFirst()
  }
  
  func down() {
    queue.insert(queue.last!, atIndex: 0)
    queue.removeLast()
  }
  
  var first: Icon? {
    get {
      return queue.first
    }
  }
  
  var exceptFirst: [Icon] {
    get {
      var ret = queue
      ret.removeFirst()
      return ret
    }
  }
  
  init(icons: [Icon]) {
    queue = icons
  }
  
}

class SwipeRadioButtonView : UIView {
  
  weak var delegate: SwipeRadioButtonViewDelegate?
  
  private var queue: IconRotateQueue
  private var insertingFirst: Icon!
  private var insertingLast:  Icon!
  
  let bigIconSize:      CGSize
  let smallIconSize:    CGSize
  let selectedSpace:    CGFloat
  let unselectedSpace:  CGFloat
  
  var UpMostSpace:      CGFloat!
  
  required init(coder: NSCoder) { fatalError() }
  override init(frame: CGRect) { fatalError() }
  
  init(origin: CGPoint, icons: [Icon], bigIconSize: CGSize, smallIconSize: CGSize, selectedSpace: CGFloat, unselectedSpace: CGFloat) {
    self.bigIconSize = bigIconSize
    self.smallIconSize = smallIconSize
    self.selectedSpace = selectedSpace
    self.unselectedSpace = unselectedSpace
    
    assert(icons.count >= 2)
    queue = IconRotateQueue(icons: icons)
    
    super.init(
      frame: CGRect(
        origin: origin,
        size: CGSizeMake(
          bigIconSize.width,
          bigIconSize.height
            + selectedSpace
            + CGFloat(icons.count)      * smallIconSize.height
            + CGFloat(icons.count - 1)  * unselectedSpace
        )))
    
    queue.first!.scaleWithSize(bigIconSize)
    UpMostSpace = frame.height * 0.05
    queue.first!.frame.origin = CGPointMake(0, UpMostSpace)
    addSubview(queue.first!)
    
    var currentTop = queue.first!.frame.maxY + selectedSpace
    
    let exceptFirst = queue.exceptFirst
    for icon in exceptFirst {
      icon.frame.origin =
        CGPointMake(
          (bigIconSize.width - smallIconSize.width) / 2,
          currentTop
        )
      icon.scaleWithSize(smallIconSize)
      addSubview(icon)
      currentTop += smallIconSize.height + unselectedSpace
    }
    
    // Inserting first
    insertingFirst = Icon(image: exceptFirst.last!.image, id: exceptFirst.last!.id)
    insertingFirst.frame.origin.x = bigIconSize.width / 2
    insertingFirst.frame.origin.y = UpMostSpace
    insertingFirst.scaleWithSize(bigIconSize)
    insertingFirst.alpha = 0.0
    addSubview(insertingFirst)
    
    // Inserting last
    insertingLast = Icon(image: queue.first!.image, id: queue.first!.id)
    insertingLast.frame.origin.x = bigIconSize.width / 2
    insertingLast.frame.origin.y = UpMostSpace + bigIconSize.height + selectedSpace
      + CGFloat(queue.exceptFirst.count - 1) * (smallIconSize.height + unselectedSpace)
    insertingLast.scaleWithSize(smallIconSize)
    insertingLast.alpha = 0.0
    addSubview(insertingLast)
    
    let drag = UIPanGestureRecognizer(target: self, action: "didDrag:")
    drag.maximumNumberOfTouches = 1
    addGestureRecognizer(drag)
  }
  
  func didDrag(sender: UIPanGestureRecognizer) {
    
    let dy = sender.translationInView(self).y
    let sign: CGFloat = dy > 0 ? 1 : -1
    let maxMoveLen = frame.height / 5
    let progress = Double(sign * min(maxMoveLen, abs(dy)) / maxMoveLen)

    
    if sender.state == .Ended {
      
      if progress <= -0.5 {
        queue.up()
        insertingFirst.image = queue.queue.last!.image
        insertingLast.image = queue.first!.image
      }
      else if progress >= 0.5 {
        queue.down()
        insertingFirst.image = queue.queue.last!.image
        insertingLast.image = queue.first!.image
      }
      else {
      }
      
      animateWithProgress(0.0)
      delegate?.didSelectIcon(queue.first!.id)
      
    }
    else if sender.state == .Changed {
      animateWithProgress(progress)
    }
  }
  
  /*
    progress: [-1, +1]
              一つ上から一つ下まで。-0.5以下 or +0.5以上で
              指を離した時にアイコンを切り替える
  */
  private func animateWithProgress(progress: Double) {
    
    assert(-1.0 <= progress && progress <= 1.0)
    
    
    guard progress != 0.0 else {
      queue.first!.scaleWithSize(bigIconSize)
      queue.first!.frame.origin.x = 0
      queue.first!.frame.origin.y = UpMostSpace
      queue.first!.alpha = 1.0
      
      let exceptFirst = queue.exceptFirst
      exceptFirst.first!.scaleWithSize(smallIconSize)
      
      for i in 0..<exceptFirst.count {
        exceptFirst[i].alpha = 1.0
        exceptFirst[i].scaleWithSize(smallIconSize)
        exceptFirst[i].frame.origin.x = (bigIconSize.width - smallIconSize.width) / 2
        exceptFirst[i].frame.origin.y = UpMostSpace + bigIconSize.height + selectedSpace
          + CGFloat(i) * (smallIconSize.height + unselectedSpace)
          + CGFloat(progress) * (smallIconSize.height + unselectedSpace)
      }
      
      insertingFirst.alpha = 0
      insertingLast.alpha = 0
      return
    }
    
    // First Icon
    queue.first!.scaleWithSize(
      CGSizeMake(
        smallIconSize.width
          + CGFloat(1.0 - abs(progress)) * (bigIconSize.width - smallIconSize.width),
        smallIconSize.height
          + CGFloat(1.0 - abs(progress)) * (bigIconSize.height - smallIconSize.height)
      )
    )
    
    queue.first!.frame.origin.x = (bigIconSize.width - queue.first!.frame.size.width) / 2
    
    if progress > 0 {
      queue.first!.frame.origin.y = UpMostSpace + (bigIconSize.height + selectedSpace) * CGFloat(progress)
    }
    else {
      queue.first!.frame.origin.y = UpMostSpace + smallIconSize.height * CGFloat(progress)
      queue.first!.alpha = CGFloat(1.0 - abs(progress))
    }
    
    
    // Other Icons
    
    // 下段1つ目
    let exceptFirst = queue.exceptFirst
    
    if progress < 0 {
      exceptFirst[0].frame.origin.y = UpMostSpace + bigIconSize.height + selectedSpace
        - (bigIconSize.height + selectedSpace) * CGFloat(abs(progress))
      exceptFirst[0].scaleWithSize(
        CGSizeMake(
          smallIconSize.width
            + CGFloat(abs(progress)) * (bigIconSize.width - smallIconSize.width),
          smallIconSize.height
            + CGFloat(abs(progress)) * (bigIconSize.height - smallIconSize.height)
        )
      )
      exceptFirst[0].frame.origin.x = (bigIconSize.width - exceptFirst[0].frame.width) / 2
    }
    else {
      exceptFirst[0].frame.origin.y = UpMostSpace + bigIconSize.height + selectedSpace
        + (smallIconSize.height + unselectedSpace) * CGFloat(progress)
    }
    
    // 下段2つ目以降
    for i in 1..<exceptFirst.count {
      exceptFirst[i].frame.origin.y = UpMostSpace + bigIconSize.height + selectedSpace
        + CGFloat(i) * (smallIconSize.height + unselectedSpace)
      + CGFloat(progress) * (smallIconSize.height + unselectedSpace)
    }
    
    // 下段最後
    if progress > 0 {
      exceptFirst.last!.alpha = CGFloat(1.0 - progress)
    }
    
    
    // 一番上と一番下にフェードで差し込んでくるアイコン
    if progress > 0 {
      insertingFirst.scaleWithSize(
        CGSizeMake(
          CGFloat(progress) * (bigIconSize.width - smallIconSize.width) + smallIconSize.width,
          CGFloat(progress) * (bigIconSize.height - smallIconSize.height) + smallIconSize.height
        )
      )
      insertingFirst.alpha = CGFloat(progress)
      insertingFirst.frame.origin.x = (bigIconSize.width - insertingFirst.frame.width) / 2
      insertingFirst.frame.origin.y = UpMostSpace - smallIconSize.height * CGFloat(1.0 - progress)
    }
    else {
      insertingLast.alpha = CGFloat(abs(progress))
      insertingLast.frame.origin.x = (bigIconSize.width - insertingLast.frame.width) / 2
      insertingLast.frame.origin.y = UpMostSpace + bigIconSize.height + selectedSpace
        + CGFloat(queue.queue.count - 1) * (smallIconSize.height + unselectedSpace)
        + CGFloat(progress) * (smallIconSize.height + unselectedSpace)
    }
  }
  
}

protocol SwipeRadioButtonViewDelegate: class {
  func didSelectIcon(iconID: String)
}

```