SwiftでUISegmentedControlのフォントと文字色を設定する方法

UISegmentedControlでフォントを設定するにはsetTitleTextAttributes。
文字色は、tintColor。
背景はbackgroundColorで良いが角丸の外まで色がつくのでclipsToBoundsを指定する必要あり。

let segmentedControl = UISegmentedControl(items: ["aaa","bbb","ccc",])

//フォント
segmentedControl.setTitleTextAttributes(NSDictionary(object: UIFont.boldSystemFontOfSize(30), forKey: NSFontAttributeName), forState: UIControlState.Normal)
        
//文字と枠の色
segmentedControl.tintColor = UIColor.yellowColor()
        
//背景
segmentedControl.backgroundColor = UIColor.redColor()
segmentedControl.layer.cornerRadius = 4 //ここは5?
segmentedControl.clipsToBounds = true

SwiftでAuto Layoutした時の座標の取り方

SwiftでAuto Layoutを設定した場合、その直後ではframeやboundsプロパティから正しい座標が取得できません。

例えば、ViewControllerにこんな感じでUIViewを作ると、4辺が全て0のRectが返されます。

override func viewDidLoad() {
    super.viewDidLoad()
    
    let viewA = UIView()
    viewA.setTranslatesAutoresizingMaskIntoConstraints(false)
    self.view.addSubview(viewA)
    let constraints = [
        NSLayoutConstraint(item: viewA, attribute: .Left, relatedBy: .Equal, toItem: self.view, attribute: .Left, multiplier: 1.0, constant: 10), //x=10
        NSLayoutConstraint(item: viewA, attribute: .Top, relatedBy: .Equal, toItem: self.view, attribute: .Top, multiplier: 1.0, constant: 10), //y=10
        NSLayoutConstraint(item: viewA, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 100), //幅=10
        NSLayoutConstraint(item: viewA, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 50), //高さ=10
    ]
    self.view.addConstraints(constraints)
    
    println(viewA.frame) <<(0.0,0.0,0.0,0.0)
}


そんな時は、layoutIfNeeded()メソッドを呼ぶと正しい座標が呼ばれるようになります。

    viewA.layoutIfNeeded()
    println(viewA.frame) <<(10.0,10.0,100.0,50.0)

ただ、UIViewに対して直接layoutIfNeeded()を呼ぶと、Viewが入れ子になっていた場合には直近のsuperviewからの座標しか再計算されません。
viewAのsubviewの画面上の座標を計算しようとしても正しく計算されません。

    subView.layoutIfNeeded()
    let displayRect = subView.convertRect(subView.bounds, toView: self.view) //self.viewのselfはViewController
    println(displayRect) <<直接の親Viewからの相対位置が返される


これを解決するには、ViewControllerのviewプロパティのlayoutIfNeeded()メソッドを呼ぶことです。

    self.view.layoutIfNeeded()
    let displayRect = subView.convertRect(subView.bounds, toView: self.view) //self.viewのselfはViewController
    println(displayRect)  <<画面上の絶対座標が取れるようになる

swift-layout

ちなみに、先々週から作っているswift-layoutでは、displayRect()で画面上の絶対座標を取得できます。

    let viewA = Layout.registUIView(superview: self.view)
        .left(10).fromSuperviewLeft()
        .top(10).fromSuperviewTop()
        .width(100)
        .height(50)
        .backgroundColor(UIColor.redColor())

    let subView = Layout.registUIView(superview: viewA.target)
        .left(30).fromSuperviewLeft()
        .top(15).fromSuperviewTop()
        .width(20)
        .height(20)
        .backgroundColor(UIColor.blueColor())

    println(subView.displayRect()) //(40.0,25.0,20.0,20.0)

SwiftでAuto Layout v0.2

先週作ったswift-layoutの機能拡張と修正をしました。

https://github.com/grachro/swift-layout


swift-layoutの使用例

//UIViewControllerでの記述例

//UILabelを画面中央に配置
let lblA = UILabel()
Layout.regist(lblA, superview: self.view)
  .horizontalCenterInSuperview()
  .verticalCenterInSuperview()
  .backgroundColor(UIColor.whiteColor())
  .text("中央")

//もう一つのラベルを画面の最下部に配置。x位置は最初のUILabelの中央と同じ
let lblB = UILabel()
Layout.regist(lblB , superview: self.view)
    .bottomIsSameSuperview())
    .horizontalCenterIsSame(lblA)
    .text("下")

v0.2修正点

containerをsuperviewに変更

UIKitの世界では、親Viewはsuperviewと表現されているので、引数とメソッド名を修正しました。



Layoutで磨りガラス効果追加

iOS7まではNavigationBarで画面を覆ったり、外部のライブラリを使ったりする必要があったようですが、UIBlurEffectで磨りガラスエフェクトをいれらるようになりました。Layoutクラスでファクトリメソッドを追加しています。

let effect1:UIVisualEffectView = Layout.createExtraLightBlurEffect()
let effect2:UIVisualEffectView = Layout.createLightBlurEffect()
let effect3:UIVisualEffectView = Layout.createDarkBlurEffect()


内部的には、UIVisualEffectViewとUIBlurEffectを読んでいるだけです。

class func createExtraLightBlurEffect() -> UIVisualEffectView {
    return UIVisualEffectView(effect: UIBlurEffect(style: .ExtraLight))
}


この方法で作る磨りガラスは厚すぎるようですが、backgroundcolorの透明度をさげても別に薄くはならないようです。


lastConstraint()でNSLayoutConstraintを取得

Layoutクラス内で生成したNSLayoutConstraintの最後のインスタンスをlastConstraintメソッドで取得できるようにしました。
後で、constantを変えたい時に使用します。


ScrollViewPullAreaでpull to requestっぽい機能を追加

テーブルの上部に引っ張ったときにヘッダ情報を出すときに使用できるScrollViewPullerクラスを試験的に作りました。
このクラスはLayout.swiftには含まれていません。ScrollViewPuller.swiftを別途追加してください。
サンプルコードのPullToRefresh1SampleViewController.swiftとかPullToRefresh2SampleViewController.swiftで動きがみれます。

SwiftでAutoLayoutを楽に書くには

JavaプログラマXcodeiPhoneアプリを作ってみる」の9週目です。

社内開発でSwiftでアプリを作っていて、デザイナーチームからいい感じのレイアウトが渡されてくるようになりました。今度出るはずのワイドなiPhoneを見据えて密かにStoryboardでAutolayoutを設定する練習を行っていたのですが、実作業1日目で挫折しました。マウスで小さいボタンやらラベルを選択するのが難しい上、相対表示の相手先がすぐにどこかわからなくなります。間隔を後から一律変更できるようにとかもしたいのですが、ちょっと調べてもやり方がわかりませんでした。

諦めて、SwiftでAutoLayoutを設定し出したのですが、NSLayoutConstraintのパラメータが多すぎます。とりあえず書けたとしても変更・保守できる自信がまったくありません。XML定義ではなく、古のJBuilderのごとくGUIコンポーネントを配置したら裏でコードを吐き出してくれれば、まだマシなのかもしれませんが。。皆さんどうやってレイアウトを設定しているのでしょうか?

仕方がないのAutoLayoutを設定しやすいようにするクラスを作って見ました。これで、デザイナーチームにストーリーボードでレイアウトを設定してもらう作業は完全に諦めることになります。


使い方

https://github.com/grachro/swift-layoutからダウンロードして、Layout.swiftだけもよりのプロジェクトへ入れてください。あとのファイルはデモ用です。
Layout.regsit(対象のUIView)で初めて、チェーン方式でレイアウトの相対位置を指定できるようになります。


簡単な例


こんな感じのレイアウト配置をしていみます。

let 起点 = UILabel()
起点.text = "起点"
Layout.regist(起点, container: self.view)
  .horizontalCenterInContainer()
  .verticalCenterInContainer()

まず、「起点」ラベルを中央に配置する方法です。
Layout.regist(対象となるUIView,対象となるUIViewのコンテナView)で初めて、horizontalCenterInContainer()で水平位置をコンテナの中央に、verticalCenterInContainer()で垂直位置をコンテナの中央に配置しています。コンテナとはレイアウト対象のUIViewの親Viewの事で、UIViewControllerでself.view.addSubview(aView)と書くときのself.viewにあたるものです。

コンテナViewを一々書かないといけないのが面倒ですがAutoLayoutの性質上こうなりました。regist()メソッドの内部では、view.setTranslatesAutoresizingMaskIntoConstraints(false)の後にcontainer.addSubview(view)を呼ぶお約束の処理を行っています。(お約束:iOSで柔軟に対応可能なレイアウトを作成できるAuto Layout入門)


let= UILabel()
右.text = "右"
Layout.regist(右, container: self.view)
  .left(20).fromRight(起点)
  .verticalCenterIsSame(起点)

次に「起点」ラベルの横に「右」ラベルを配置します。
left(20).fromRight(起点)のところが、「右」ラベルの左辺が「起点」ラベルの右辺から20の距離であることを指定しています。
verticalCenterIsSame(起点)で、垂直位置が「起点」ラベルと同じになります。「起点」ラベルの垂直位置はコンテナの中央なので、ここは.verticalCenterInContainer()と書いても同じはずです。


let= UILabel()
左.text = "左"
Layout.regist(左, container: self.view)
  .right(20).fromLeft(起点)
  .verticalCenterIsSame(起点)

反対側に「左」ラベルを配置します。
right(20).fromLeft(起点)で、「左」ラベルの右辺が「起点」ラベルの左辺から20の距離となります。


let= UILabel()
上.text = "上"
Layout.regist(上, container: self.view)
  .bottom(20).fromTop(起点)
  .horizontalCenterIsSame(起点)


let= UILabel()
下.text = "下"
Layout.regist(下, container: self.view)
  .top(20).fromBottom(起点)
  .horizontalCenterIsSame(起点)

同様に「上」「下」ラベルを配置します。



コンテナからの相対位置の指定方法


次に、このレイアウトを設定してみます。

let lbl左上 = UILabel()
lbl左上.text = "左上"
Layout.regist(lbl左上, container: self.view)
  .top(20).fromContainerTop()
  .left(20).fromContainerLeft()

let lbl右下 = UILabel()
lbl右下.text = "右下"
Layout.regist(lbl右下, container: self.view)
  .bottom(20).fromContainerBottom()
  .right(20).fromContainerRight()

コンテナからの相対位置を指定する場合は、.top(距離).fromTop(コンテナView)の代わりに.top(距離).fromContainerTop()とかけます。
他の3辺も同様です。



位置が同じことを指定する方法

サンプルのレイアウトイメージは先ほどと同じです。

let lbl左 = UILabel()
lbl左.text = "左"
Layout.regist(lbl左, container: self.view)
  .verticalCenterIsSame(lbl中央)
  .leftIsSame(lbl左上)

「左」ラベルの左辺は「左上」ラベルと同じですが、.left(0).fromLeft(lbl左上) と書く代わりに、.leftIsSame(lbl左上) とかけます。
同様に、.rightIsSame(対象ラベル)、.topIsSame(対象ラベル)、.bottomIsSame(対象ラベル)で各編の位置を指定できます。



長いですが、2つ目の画面イメージを設定するコードです

let lbl左上 = UILabel()
lbl左上.text = "左上"
Layout.regist(lbl左上, container: self.view)
    //.top(20).fromContainerTop()
    .top(20).fromTop(self.view)
    .left(20).fromContainerLeft()


let lbl右上 = UILabel()
lbl右上.text = "右上"
Layout.regist(lbl右上, container: self.view)
    .top(20).fromContainerTop()
    .right(20).fromContainerRight()


let lbl左下 = UILabel()
lbl左下.text = "左下"
Layout.regist(lbl左下, container: self.view)
    .bottom(20).fromContainerBottom()
    .left(20).fromContainerLeft()


let lbl右下 = UILabel()
lbl右下.text = "右下"
Layout.regist(lbl右下, container: self.view)
    .bottom(20).fromContainerBottom()
    .right(20).fromContainerRight()


let lbl中央 = UILabel()
lbl中央.text = "中央"
Layout.regist(lbl中央, container: self.view)
    .horizontalCenterInContainer()
    .verticalCenterInContainer()


let lbl左 = UILabel()
lbl左.text = "左"
Layout.regist(lbl左, container: self.view)
    .verticalCenterIsSame(lbl中央)
    .leftIsSame(lbl左上)


let lbl右 = UILabel()
lbl右.text = "右"
Layout.regist(lbl右, container: self.view)
    .verticalCenterIsSame(lbl中央)
    .rightIsSame(lbl右上)

let lbl上 = UILabel()
lbl上.text = "上"
Layout.regist(lbl上, container: self.view)
    .topIsSame(lbl左上)
    .horizontalCenterIsSame(lbl中央)

let lbl下 = UILabel()
lbl下.text = "下"
Layout.regist(lbl下, container: self.view)
    .bottomIsSame(lbl右下)
    .horizontalCenterIsSame(lbl中央)

等間隔に配置


コンテナ内限定ですが、等間隔に配置もできます。

var l1 = UILabel()
l1.text = "l1"
l1.backgroundColor = UIColor.redColor()
Layout.regist(l1, container: self.view)
    .verticalCenterInContainer()
    .width(30)
    .height(50)

var l2 = UILabel()
l2.text = "l2"
l2.backgroundColor = UIColor.greenColor()
Layout.regist(l2, container: self.view)
    .verticalCenterIsSame(l1)
    .widthIsSame(l1)
    .height(60)


var l3 = UILabel()
l3.text = "l3"
l3.backgroundColor = UIColor.blueColor()
Layout.regist(l3, container: self.view)
    .verticalCenterIsSame(l1)
    .widthIsSame(l1)
    .height(60)

Layout.horizontalEvenSpaceInCotainer(container: self.view, views: [l1,l2,l3], coverSpace: true)

Layout.horizontalEvenSpaceInCotainer(コンテナView, 並べるUIViewの配列, UIViewの周りに空白を置くか)で横方向に等間隔に並びます。



coverSpaceがFalseの場合は周りのスペースが作られません。




縦方向へはLayout.verticalEvenSpaceInCotainer(...)で並べられます。



幅、高さを固定

上の例で出てきていますが、幅と高さを固定値で指定したい場合は、.width(値)とか.height(値)で指定してください。



その他

Layoutとは直接関係ない機能もいくつかあります。


UILabelの文字を寄せる方向を指定(チェーン内で使用)

  • textAlignmentIsLeft()
  • textAlignmentIsCenter()
  • textAlignmentIsRight()


システムタイプのUIButtonを生成(チェーンとは関係なく使用します)

  • Layout.createSystemTypeBtn(title:String) -> UIButton


自動改行するのUILabelを生成(チェーンとは関係なく使用します)

  • Layout.createWordWrappingLabel(text:String) -> UILabel
  • Layout.createCharWrappingLabel(text:String) -> UILabel


ボタンのタップイベントにブロックを渡す
サンプルプロジェクト内の各UIViewContoroller内で、TouchBlocksを使っている箇所をみてください。
使い勝手がいまいちです。

最後に

動作確認はiPod toucheでランドスケープさせて行っています。
バグ等ありましたらお知らせください。

Swift開発用簡易ランチャ

JavaプログラマXcodeiPhoneアプリを作ってみる」の8週目です。

Swiftの開発でビジネスロジック寄りのコードの動作をさっとしたい場合があります。デバッグ用のViewをストーリーボードに作って毎回新しいボタンを付けていたのですが、画面が手狭になったり、規模が大きくなるにつけて掃除をするのが面倒になったりしてきました。xUnitでテストしようかとも思ったのですが、ミドルウェア周りの準備でやっぱり無駄な時間がかかってしまいます。

それで、関数を引数渡しする練習も兼ねて、TableViewの項目をクリックすれば任意の処理を実行できる、簡易ランチャを作りました。思いっきりControllerクラスにオンコードで処理を書いていますが、とりあずストーリーボードを触らなくても実行できるテストが増やせるようになりました。



とりあえず全文

import UIKit

typealias Command = (name:String, body:() -> Void)

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource  {
                            
    @IBOutlet weak var tableView: UITableView!
    
    var commandlist:[Command] = []
    
    private func addCommand(name:String, body:() -> Void = {}){
        commandlist.append(Command(name:name, body:body))
    }
    
    
     //UIViewController
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.tableView.delegate = self;
        self.tableView.dataSource = self
        
        //ここからコマンドリストの作成
        addCommand("処理A",body:{
            //処理A
        })
        
        addCommand("処理B",body:{
            //処理B
        })
        
        addCommand("処理C",body:{
            //処理B
        })
    }

     //UIViewController
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    //UITableViewDataSource
    func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
        return commandlist.count
    }
    
    //UITableViewDataSource
    func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
        
        let command = commandlist[indexPath.row]
        
        let cell = UITableViewCell()
        cell.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator
        cell.textLabel.text = "\(command.name)"
        
        return cell
    }
    
    //UITableViewDataSource
    func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
        
        let command = commandlist[indexPath.row]
        command.body()
        
    }
}

解説

名前と関数が組みのタプルを作ります。
ここではCommandと別名をつけました。

typealias Command = (name:String, body:() -> Void)

タプルのリストを作ります。

var commandlist:[Command] = []


画面起動時にタプルのリストに処理を追加していきます。

        //ここからコマンドリストの作成
        addCommand("処理A",body:{
            //処理A
        })
        
        addCommand("処理B",body:{
            //処理B
        })
        
        addCommand("処理C",body:{
            //処理B
        })

クリックされたらタプルの関数を実行します。

    //UITableViewDataSource
    func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
        
        let command = commandlist[indexPath.row]
        command.body()
        
    }

さらに解説

Commandの関数でタプルのリストを変更することにより、画面に表示されているコマンドを変更することができます。
これを再帰的に行えば、大カテゴリの一覧を表示 > 小カテゴリの一覧を表示 > 各項目のプロパティの一覧を標示 といったことができるようになるので、業務的なアプリのテスト実装だと割とこれだけで間に合ったりします。

     //UIViewController
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.tableView.delegate = self;
        self.tableView.dataSource = self
        
        changeMainList()
    }

    func changeMainList() {
        
        commandlist.removeAll(keepCapacity: false)
        
        addCommand("処理A(メッセージ表示)",body:{
            var alert = UIAlertView()
            alert.message = "ここに処理Aの実装"
            alert.addButtonWithTitle("OK")
            alert.show()
        })
        
        addCommand("処理B(サブリストBへ)",body:{
            self.changeSublistB()
        })
        
        self.tableView.reloadData()
    }
    
    
    func changeSublistB() {
        commandlist.removeAll(keepCapacity: false)
        
        addCommand("サブリストB [1項目]", body:{/*クリックしても特に何もしない*/})
        
        addCommand("サブリストB [2項目]", body:{/*クリックしても特に何もしない*/})
        
        addCommand("戻る", body:{
            self.changeMainList()
        })
        
        self.tableView.reloadData()
    }


changeMainList()で処理Aをクリック


changeMainList()で処理Bをクリックして、サブリストBを標示

動くプロジェクトはここに置きました

https://github.com/grachro/junk201408-command

JavaプログラマがXcodeでiPhoneアプリを作ってみる.7週目

無限リストを作成する練習をしてみました。
具体的なコードは最後に載せています。実際に動作するプロジェクトもGitHubにあげました。

動作

  1. 最初に10データ表示
  2. リストの最後が表示されると、最後尾に自動で5行追加
  3. リストの最初で引っ張られると、先頭に自動で2行追加

1.最初に10データ表示

Cellの使い回しを自動的にやってくれるらしいのでUITableViewControllerやUITableViewを使います。重くなるを防げるようになります。
モデルとしてDataクラス、モデルのリストにdataArray:[Data]を準備して、画面表示時にdataArrayに10データ作成しておきます。



2.リストの最後が表示されると、最後尾に自動で5行追加

StoryBoard

Prototype Cellをデータ用(identifier:dataCell)とフッター用(identifier:fotterCell)の2つ用意します。

numberOfSectionsInTableView

2を返すように設定します。

cellForRowAtIndexPath

section=0の時はデータ用、section=1の時はフッター用のCellを返すようにします。

willDisplayCell

再表示を含めセルが表示されるときには、willDisplayCellラベルをもつtableViewメソッドが呼ばれるようです。
section=1のときにdataArrayに5行追加してテーブル再更新をすれば完了です。



3.リストの最初で引っ張られると、先頭に自動で2行追加

引っ張る動作は、すでにフレームワークで準備されていました。出来合いの動作でいいのであれば、UIRefreshControlをTableViewに追加して、addTargetでコールバックするメソッド名を指定するだけです。便利です。
コールバックイベントが発生した時に、self.dataArray.insert(newData, atIndex: 0)で単純にデータ追加でとりあえずそれっぽい動きをするようになりました。

ただこの方法だとindexPathがUITableViewが管理している内容とずれるはずなので、Cellクラスを複数使う場合には、tableView.dequeueReusableCellWithIdentifier()の箇所で各Cellクラスにキャストする時につじつまが合わなくなるような気がしますが今週はここまで。


ソース

https://github.com/grachro/junk201408-swift-TableView-InfiniteScroll

//
//  ViewController.swift
//  junk201408-swift-TableView-InfiniteScroll
//
//  Created by grachro on 2014/08/24.
//  Copyright (c) 2014年 grachro. All rights reserved.
//

import UIKit

class ViewController: UIViewController ,UITableViewDelegate, UITableViewDataSource {
    
    @IBOutlet weak var tableView: UITableView!

    let refreshControl = UIRefreshControl()
    
    let DETA_SECTION = 0
    let FOTTER_SECTION = 1
    
    var dataArray:[Data] = [] //モデルのリスト
    let INIT_DATA_COUNT = 10 //初期表示データ
    let ADD_DATA_COUNT = 5 ///最後尾が表示された時に追加するデータ数
    let REFLESH_DATA_COUNT = 2 ///引っ張られた時に上に追加するデータ数
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        addDataAfter(INIT_DATA_COUNT)
        
        self.refreshControl.addTarget(self, action: "callbackRefreshControl", forControlEvents: UIControlEvents.ValueChanged)
        
        self.tableView.delegate = self;
        self.tableView.dataSource = self
        self.tableView.addSubview(self.refreshControl)
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    //UITableViewDataSource
    func numberOfSectionsInTableView(tableView: UITableView!) -> Int {
        return 2
    }

    //UITableViewDataSource
    func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
        
        switch section {
            case DETA_SECTION:return self.dataArray.count
            case FOTTER_SECTION:return 1
            default:return 0
        }
  
    }

    //UITableViewDataSource
    func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
        
        switch indexPath.section {
        case DETA_SECTION:
            let cell = tableView.dequeueReusableCellWithIdentifier("dataCell", forIndexPath: indexPath) as DataCell
            
            let data = self.dataArray[indexPath.row]
            cell.setCaption("Dataセル セクション:\(data.index)  \(data.caption)")
            
            return cell
        case FOTTER_SECTION:
            let cell = tableView.dequeueReusableCellWithIdentifier("fotterCell", forIndexPath: indexPath) as UITableViewCell
            return cell
        default:return UITableViewCell()
        }

    }
    
    //UITableViewDelegate
    func tableView(tableView: UITableView!, heightForRowAtIndexPath indexPath: NSIndexPath!) -> CGFloat {
        return 40
    }
    
    //UITableViewDelegate
    func tableView(tableView: UITableView!, willDisplayCell cell: UITableViewCell!, forRowAtIndexPath indexPath: NSIndexPath!) {

        //2つ目のセクションが表示されたら、モデルにデータ追加
        if indexPath.section == FOTTER_SECTION {
            addDataAfter(ADD_DATA_COUNT)
            self.tableView.reloadData()
        }
    }

    //モデル操作
    private func addDataAfter(count:Int) {
        for i in 0..<count {
            let last:Data? = self.dataArray.last
            let baseIndex = last == nil ? -1 : last!.index
            
            let newIndex = baseIndex + 1
            let newData = Data(index: newIndex,caption: "\(newIndex)行目")
            self.dataArray.append(newData)
        }
    }
    
    //UIRefreshControl
    func callbackRefreshControl(){
        addDataBefore(REFLESH_DATA_COUNT)
        self.tableView.reloadData()
        self.refreshControl.endRefreshing()
    }
    
    //モデル操作
    private func addDataBefore(count:Int) {
        for i in 0..<count {
            let first:Data = self.dataArray.first!
            let baseIndex = first.index
            
            let newIndex = baseIndex - 1
            let newData = Data(index: newIndex,caption: "\(newIndex)行目")
            self.dataArray.insert(newData, atIndex: 0)
        }
    }

}

 

 

JavaプログラマがXcodeでiPhoneアプリを作ってみる.6週目

せっかくなので関数型で書いてみる

何も考えずにSwiftを書いているといつものオブジェクト指向で書いてしまいます。せっかくなのでArrayクラスで関数型の書き方を試してみます。



Array.map()

map()は配列の各要素に同じ処理を繰り返す場合に使用します。
メドッド名が何故map()なのかニュアンスがよくわかりません。

1.関数型言語は関数を引数で渡せます。

//100を足して返す関数

func plus100(srcVal:Int) -> Int {
  return srcVal + 100
}

//Arrayのmapメソッドに関数を渡す

let srcArray = [1, 2, 3, ]
var array1 = srcArray.map(plus100)
XCTAssertEqual(array1[0], 101, "Pass")
XCTAssertEqual(array1[1], 102, "Pass")
XCTAssertEqual(array1[2], 103, "Pass")


2.同じ処理をクロージャでも書けます
変数名とか型を省略して書くとこんな感じになります

let srcArray = [1, 2, 3, ]
let array2 = srcArray.map{ $0 + 100 }
XCTAssertEqual(array2[0], 101, "Pass")
XCTAssertEqual(array2[1], 102, "Pass")
XCTAssertEqual(array2[2], 103, "Pass")


3.変数名と型を明示して書くこともできます。これだと戻り値の型を変えられます。

var array3 = srcArray.map{(srcVal:Int) -> String in
  return String(srcVal + 100)
}
XCTAssertEqual(array3[0], "101", "Pass")
XCTAssertEqual(array3[1], "102", "Pass")
XCTAssertEqual(array3[2], "103", "Pass")


4.クロージャはブロックの外の変数にアクセス可能です

var before = 0
var 配列の一つ前の要素と合計 = srcArray.map{(srcVal:Int) -> Int in
  let now = before + srcVal
  before = srcVal
  return now
}
XCTAssertEqual(配列の一つ前の要素と合計[0], 1, "Pass") //0 + 1
XCTAssertEqual(配列の一つ前の要素と合計[1], 3, "Pass") //1 + 2
XCTAssertEqual(配列の一つ前の要素と合計[2], 5, "Pass") //2 + 3
Array.filter()

filterは、文字通り任意の基準で配列の要素を抽出するのに使用します。

let srcArray = [1, 2, 3, 4, 5, 6, 7, 8, 9,]

let 3の倍数 = srcArray.filter{ $0 % 3 == 0 }
XCTAssertEqual(3の倍数[0], 3, "Pass")
XCTAssertEqual(3の倍数[1], 6, "Pass")
XCTAssertEqual(3の倍数[2], 9, "Pass")
XCTAssertEqual(3の倍数.count, 3, "Pass")
Array.reduce()

reduceは前のループの結果を受けて次のループを処理するときに使用する感じでしょうか?
最初の引数の値は初期値となります

let srcArray = [1, 2, 3, ]
let sum1 = srcArray.reduce(0) { $0 + $1 }
XCTAssertEqual(sum1, 6, "Pass") //初期値0 + 1 + 2 + 3

let sum2 = srcArray.reduce(100) { $0 + $1 }
XCTAssertEqual(sum2, 106, "Pass") //初期値100 + 1 + 2 + 3
Array.sorted()

最後はソートです。sortedだと新たなリストを返しますが、sort()だと元の配列自体が並び替えられます。

var srcArray = [3, 1, 4, 2,]
    
let ソートしてみた = srcArray.sorted({ $0 < $1 })
XCTAssertEqual(ソートしてみた[0], 1, "Pass")
XCTAssertEqual(ソートしてみた[1], 2, "Pass")
XCTAssertEqual(ソートしてみた[2], 3, "Pass")
XCTAssertEqual(ソートしてみた[3], 4, "Pass")
おまけ

Arrayのコード補完で表示されるメソッドのうち、関数が渡せるメソッドはあと2つあります。Array.withUnsafeBufferPointer(),ArrayとArray.withUnsafeMutableBufferPointer()です。ググっても情報がありません。かなりフレームワークよりの機能なのでしょうか?