スクロールビューに動的に要素を生成・追加する（UIKit::UIScrollView）

# スタックビューに動的に要素を追加する
[元ネタ](https://developer.apple.com/jp/documentation/UserExperience/Conceptual/AutoLayoutGuide/LayoutUsingStackViews/LayoutUsingStackViews.html#//apple_ref/doc/uid/TP40010853-CH11-SW1)

## Single View Applicationでプロジェクトを作成
Project Nameは適当に

## 初期UI要素配置
1. Scroll Viewを配置して制約を設定
  - TopをContrain to Marginにチェックを入れ、Top Layout Guideから0に設定（Scroll View.Top = Superview.TopMargin）
 - LeadingをContrain to Marginにチェックを入れ、Viewから0に設定（Scroll View.Leading = Superview.LeadingMargin）
 - TrailingをContrain to Marginにチェックを入れ、Viewから0に設定（Scroll View.Trailing = Superview.TrailingMargin）
 - BottomをContrain to Marginにチェックを入れ、Bottom Layout Guideから20に設定（Bottom Layout Guide.Top = Scroll View.Bottom + 20.0）
2. Scroll ViewにVertical Stack Viewを配置して制約を設定
  - TopをContrain to Marginのチェックを外し、Scroll Viewから0に設定（Stack View.Top = Scroll View.Top）
 - LeadingをContrain to Marginのチェックを外し、Scroll Viewから0に設定（Stack View.Leading = Scroll View.Leading）
 - TrailingをContrain to Marginのチェックを外し、Scroll Viewから0に設定（Stack View.Trailing = Scroll View.Trailing）
 - BottomをContrain to Marginのチェックを外し、Scroll Viewから0に設定（Stack View.Bottom = Scroll View.Bottom）
 - Scroll ViewとStack Viewを選択し、Equal Widthsを設定（Stack View.Width = Scroll View.Width）
3. Stack Viewの属性を設定
 - Axis = Vertical
 - Alignment = Fill
 - Distribution = Equal Spacing
 - Spacing = 0
4. Stack ViewにButtonを配置し、Titleを「add item」に設定

## Scroll ViewのOutletを作成

Outletとは、ストーリーボード上のオブジェクトの参照のこと（[アウトレット - Swift による iOS 開発入門](http://swift-ios.keicode.com/ios/outlet.php)）

1. Xcode画面右上のShow the Assistant Editorを選択して、ViewController.swiftのコードを表示（右のUtilitiesは閉じておくと作業しやすいかもしれない）
2. Scroll Viewを選択し、Controlを押しながらエディターのほうにドラッグしていき、以下の場所にドロップし、Connect後、privateに設定

```swift:ViewController.swift
class ViewController: UIViewController {
    //ここにドロップ
    override func viewDidLoad() {
```

操作方法は以下を参照
[Swiftでの変数の定義、Xcodeでのアウトレット接続、iOSシミュレーターへの画像素材追加](http://www.atmarkit.co.jp/ait/articles/1501/20/news023.html)

Outletの設定

 - Connection = Outlet
 - Object = View Controller
 - Name = scrollView
 - Type = UIScrollView
 - Storage = Weak

コードを追加してprivateに

```swift:ViewController.swift
class ViewController: UIViewController {

    @IBOutlet weak private var scrollView: UIScrollView!

    override func viewDidLoad() {
```

## Stack ViewのOutletを作成

Scroll Viewと同様に、以下のようにStack ViewのOutletを作成

```swift:ViewController.swift
class ViewController: UIViewController {

    @IBOutlet weak private var scrollView: UIScrollView!
    @IBOutlet weak private var stackView: UIStackView!

    override func viewDidLoad() {
```

## Scroll Viewの初期スクロール位置設定

viewDidLoadを以下のように変更

```swift:ViewController.swift
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // setup scrollview
        let insets = UIEdgeInsetsMake(20.0, 0.0, 0.0, 0.0)
        scrollView.contentInset = insets
        scrollView.scrollIndicatorInsets = insets
    }
```

### 補足

viewDidLoadは、ViewControllerクラスがインスタンス化された直後に呼び出されるメソッド
[UIEdgeInsetsMake](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIKitFunctionReference/#//apple_ref/c/func/UIEdgeInsetsMake)はUIKitフレームワークに用意された関数で、ButtonやViewの周囲にマージンを設定
> `func UIEdgeInsetsMake(_ top: CGFloat, _ left: CGFloat, _ bottom: CGFloat, _ right: CGFloat) -> UIEdgeInsets`

[contentInset](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIScrollView_Class/#//apple_ref/occ/instp/UIScrollView/contentInset)はUIScrollViewクラスのプロパティで、Scroll View内の要素のマージンを規定
> `var contentInset: UIEdgeInsets`

[scrollIndicatorInsets](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIScrollView_Class/#//apple_ref/occ/instp/UIScrollView/scrollIndicatorInsets)はUIScrollViewクラスのプロパティで、スクロールバーのマージンを規定
> `var scrollIndicatorInsets: UIEdgeInsets`

## add itemボタンのAction作成

Controlを押しながらadd itemを以下の場所にドロップし、Connect

```swift:ViewController.swift
        scrollView.scrollIndicatorInsets = insets
    }

    // ここにドロップ
```

Actionの設定

 - Connection = Action
 - Object = View Controller
 - Name = addEntry
 - Type = AnyObject
 - Event = Touch Up Inside
 - Arguments = Sender

## add itemボタンのAction設定

addEntryの中身を以下のように変更

```swift:ViewController.swift
    @IBAction func addEntry(sender: AnyObject) {
        
        let stack = stackView
        let index = stack.arrangedSubviews.count - 1
        let addView = stack.arrangedSubviews[index]
        
        let scroll = scrollView
        let offset = CGPoint(x: scroll.contentOffset.x, y: scroll.contentOffset.y + addView.frame.size.height)
        
        let newView = createEntry()
        newView.hidden = true
        stack.insertArrangedSubview(newView, atIndex: index)
        
        UIView.animateWithDuration(0.25) { () -> Void in
            newView.hidden = false
            scroll.contentOffset = offset
        }
    }
```

### 補足

[arrangedSubviews](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIStackView_Class_Reference/#//apple_ref/occ/instp/UIStackView/arrangedSubviews)はUIStackView内の要素を格納したsubviewsのリスト
> `var arrangedSubviews: [UIView] { get }`

[CGPoint](https://developer.apple.com/library/ios/documentation/GraphicsImaging/Reference/CGGeometry/#//apple_ref/c/tdef/CGPoint)はCore Graphicsフレームワークに用意された構造体で、二次元の座標を規定
> `struct CGPoint { var x: CGFloat var y: CGFloat init() init(x x: CGFloat, y y: CGFloat) }`

[contentOffset](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIScrollView_Class/#//apple_ref/occ/instp/UIScrollView/contentOffset)はUIScrollViewクラスのプロパティで、Scroll View内での位置を規定
> `var contentOffset: CGPoint`

[insertArrangedSubview](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIStackView_Class_Reference/#//apple_ref/occ/instm/UIStackView/insertArrangedSubview:atIndex:)はUIStackViewクラスのメッソドで、指定されたインデックスの位置にsubviewを追加
> `func insertArrangedSubview(_ view: UIView, atIndex stackIndex: Int)`

[animateWithDuration](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/#//apple_ref/occ/clm/UIView/animateWithDuration:animations:)はUIViewクラスのメソッドで、Viewの変化のアニメーションを規定
> `class func animateWithDuration(_ duration: NSTimeInterval, animations animations: () -> Void)`

## 削除機能追加

以下のコードを追加

```swift:ViewController.swift
            scroll.contentOffset = offset
        }
    }
    
    func deleteStackView(sender: UIButton) {
        if let view = sender.superview {
            UIView.animateWithDuration(0.25, animations: { () -> Void in
                view.hidden = true
                }, completion: { (success) -> Void in
                    view.removeFromSuperview()
                })
        }
    }
```

### 補足

[removeFromSuperview](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/#//apple_ref/occ/instm/UIView/removeFromSuperview)はUIViewのメソッドで、view自身を削除
> `func removeFromSuperview()`

## Entry Viewの中身作成

以下のコードを追加

```swift:ViewController.swift
                    view.removeFromSuperview()
                })
        }
    }
    
    private func createEntry() -> UIView {
        let date = NSDateFormatter.localizedStringFromDate(NSDate(), dateStyle: .ShortStyle, timeStyle: .NoStyle)
        let number = "\(randomHexQuad())-\(randomHexQuad())-\(randomHexQuad())-\(randomHexQuad())"
        
        let stack = UIStackView()
        stack.axis = .Horizontal
        stack.alignment = .FirstBaseline
        stack.distribution = .Fill
        stack.spacing = 8
        
        let dateLabel = UILabel()
        dateLabel.text = date
        dateLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleBody)
        
        let numberLabel = UILabel()
        numberLabel.text = number
        numberLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)
        
        let deleteButton = UIButton(type: .RoundedRect)
        deleteButton.setTitle("Delete", forState: .Normal)
        deleteButton.addTarget(self, action: "deleteStackView:", forControlEvents: .TouchUpInside)
        
        stack.addArrangedSubview(dateLabel)
        stack.addArrangedSubview(numberLabel)
        stack.addArrangedSubview(deleteButton)
        
        return stack
    }
    
    private func randomHexQuad() -> String {
        return NSString(format: "%X%X%X%X",
            arc4random() % 16,
            arc4random() % 16,
            arc4random() % 16,
            arc4random() % 16
        ) as String
    }
```

[NSDateFormatter](https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSDateFormatter_Class/)はFoundationフレームワークのクラスで、NSDateオブジェクトを作成
[localizedStringFromDate](https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSDateFormatter_Class/#//apple_ref/occ/clm/NSDateFormatter/localizedStringFromDate:dateStyle:timeStyle:)はNSDateFormatterクラスのメソッドで、指定されたフォーマットで、現在のロケールでの特定の日時を表す文字列を返す
> ```
class func localizedStringFromDate(_ date: NSDate,
                         dateStyle dstyle: NSDateFormatterStyle,
                         timeStyle tstyle: NSDateFormatterStyle) -> String
```

参考：[NSDateFormatterStyleの種類](https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSDateFormatter_Class/#//apple_ref/c/tdef/NSDateFormatterStyle)

## 注意事項

- 非表示のビューも、スタックが管理する配置対象ビューの配列に入っています。しかし画面には表示されず、他の配置対象ビューのレイアウトにも影響を及ぼしません。
- あるビューを、スタック側の配置対象ビューの配列に追加すると、ビュー階層にも自動的に追加されます。
- ビューをスタック側の配列から削除しても、ビュー階層から自動的に削除されることはありません。一方、ビュー階層からビューを削除すれば、スタック側の配列からも削除されます。
- 通常、ビューのhiddenプロパティは、アニメーション可能（animatable、値を変更すると自動的にアニメーションが実行される）でありません。しかし、スタック側の配置対象ビューの配列にビューを置けば、アニメーション可能になります。実際にアニメーションを管理するのは、ビューではなくスタックです。hiddenプロパティを使って、ビューを追加/削除する際のアニメーション表示をしてください。