Cocoa練習帳 -3ページ目

[Swifty]総称型

ギブアップ。そもそもの構造が違うので、発表のとおりの修正は無理。

発表では、DataSourceに総称型を導入。

class DataSource: NSObject, UITableViewDataSource, SourceType {
    var dataObject: DataType
    
    init(dataObject: A) {
        self.dataObject = dataObject
    }
}

そうすると、初期化は以下のようになる。

class HandDataSource: DataSource {
    init() {
        super.init(dataObject: Hand())
    }
}

ただ、何度も言い訳をして苦しいが、自分のプロジェクトでは、Model(Dataコントローラ)のクラスを用意しているので、こうはならない。

ソースコード GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/ios/Hand - GitHub
関連情報 文化を調和させる
【Cocoa練習帳】 http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)

[Swifty]DataSourceのサブクラス

今回も辛いはず!

DataSourceを継承したHandDataSourceを作る。

class HandDataSource: DataSource {
    override init() {
        super.init()
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath as IndexPath) as? CardCell else {
            return UITableViewCell()
        }
        let card = document.getItem(at: indexPath.row)
        cell.fillWith(card: card)
        return cell
    }
}

ハンドビューコントローラがこれを利用するようにする。

class HandVC: UITableViewController {
    private var dataSource = HandDataSource()
}

スーパークラスを変更する。

class DataSource: NSObject, UITableViewDataSource, SourceType {
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        fatalError("This method must be overriden")
    }
}

継承されなかったら、エラーにするということだ。

ソースコード GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/ios/Hand - GitHub
関連情報 文化を調和させる
【Cocoa練習帳】 http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)

[Swifty]計算済みプロパティの追加

かなり無理をしている状態になってきたが、それだけ、試行錯誤しないといけないので、得るものはある。

今回は、計算済みプロパティの追加だ。今回も、無理がありそう!

やはり、これは無理。

protocol SourceType: UITableViewDataSource {
    //var dataObject: DataType {get set}
    //var conditionForAdding: Bool {get}
}

Model(Dataコントローラ)に計算済みプロパティを追加することに変更。

class Document: NSObject {
    private var dataObject: DataType = Hand()
    
    var conditionForAdding: Bool {
        return dataObject.numberOfItems < 5
    }
}

すると、こうなる。

class DataSource: NSObject, UITableViewDataSource, SourceType {
    private var document = Document.sharedInstance
    
    func addItemTo(tableView: UITableView) {
        if document.conditionForAdding {
            document.addNewItem(at: 0)
            insertTopRowIn(tableView: tableView)
        }
    }
}

条件判定はプロパティになっている。

ソースコード GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/ios/Hand - GitHub
関連情報 文化を調和させる
【Cocoa練習帳】 http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)

[Swifty]Handを取り除く

今回は難航した。元の発表はViewControllerでHandなどのデータ型に依存したコードを取り除くという内容だったが、自分のコードは、Model(Dataコントローラ)を用意しているので、データ型に依存していない。無理やり、Modelに適用してみたが、ちょっと、辛い。

DataTypeによって、CardがItemと、特定のデータ型に依存しないものになる。

class Document: NSObject {
    private var dataObject: DataType = Hand()
    
    public var numberOfItems: Int {
        return dataObject.numberOfItems
    }
    
    public func addNewItem(at index: Int) {
        dataObject = dataObject.addNewItem(at: index)
    }
    
    public func getItem(at index: Int) -> Card {
        guard let hand = dataObject as? Hand else {
            fatalError("Could not create Card Cell or Hand instance")
        }
        return hand[index]
    }
    
    public func deleteCard(at index: Int) {
        dataObject = dataObject.deleteItem(at: index)
    }
}

かなり、辛い。

protocol SourceType: UITableViewDataSource {
    //var dataObject: DataType {get set}
    func insertTopRowIn(tableView: UITableView)
    func deleteRowAtIndexPath(indexPath: NSIndexPath, from tableView: UITableView)
}
 
extension SourceType {
    func insertTopRowIn(tableView: UITableView) {
        let indexPath = IndexPath(row: 0, section: 0)
        tableView.insertRows(at: [indexPath], with: .fade)
    }
    
    func deleteRowAtIndexPath(indexPath: NSIndexPath, from tableView: UITableView) {
        tableView.deleteRows(at: [indexPath as IndexPath], with: .fade)
    }
}
class DataSource: NSObject, UITableViewDataSource, SourceType {
    private var document = Document.sharedInstance
    
    func addItemTo(tableView: UITableView) {
        if document.numberOfItems < 5 {
            document.addNewItem(at: 0)
            insertTopRowIn(tableView: tableView)
        }
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return document.numberOfItems
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CardCell
        let card = document.getItem(at: indexPath.row)
        cell.fillWith(card: card)
        return cell
    }
    
    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == .delete {
            document.deleteCard(at: indexPath.row)
            tableView.deleteRows(at: [indexPath], with: .fade)
        } else if editingStyle == .insert {
            // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
        }
    }
}
class MasterViewController: UITableViewController {
 
    private var dataSource = DataSource()
    var detailViewController: DetailViewController? = nil
 
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        tableView.dataSource = dataSource
        self.navigationItem.leftBarButtonItem = self.editButtonItem
 
        let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(MasterViewController.addNewCard(sender:)))
        self.navigationItem.rightBarButtonItem = addButton
        if let split = self.splitViewController {
            let controllers = split.viewControllers
            self.detailViewController = (controllers[controllers.count-1] as! UINavigationController).topViewController as? DetailViewController
        }
    }
 
    override func viewWillAppear(_ animated: Bool) {
        self.clearsSelectionOnViewWillAppear = self.splitViewController!.isCollapsed
        super.viewWillAppear(animated)
    }
 
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
 
    @IBAction private func addNewCard(sender: UIBarButtonItem) {
        dataSource.addItemTo(tableView: tableView)
    }
 
    // MARK: - Segues
 
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "showDetail" {
            if let indexPath = self.tableView.indexPathForSelectedRow {
                let card = Document.sharedInstance.getItem(at: indexPath.row)
                let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
                controller.detailItem = card
                controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem
                controller.navigationItem.leftItemsSupplementBackButton = true
            }
        }
    }
 
    // MARK: - Table View
 
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
 
    override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
        // Return false if you do not want the specified item to be editable.
        return true
    }
}

今回は、効果がよく分からないものになってしまった。

ソースコード GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/ios/Hand - GitHub
関連情報 文化を調和させる
【Cocoa練習帳】 http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)

[Swifty]モデルもプロトコルへ

モデルのHandをDataTypeプロトコル適用ということにして、項目名をCardから汎用的なItemに変更する。

protocol DataType {
    var numberOfItems: Int {get}
    func addNewItem(at index: Int) -> Self
    func deleteItem(at index: Int) -> Self
    func moveItem(fromIndex: Int, toIndex: Int) -> Self
}
struct Hand: DataType {
    private var deck = Deck()
    private var cards = [Card]()
    
    public init() {
    }
    
    public init(deck: Deck, cards: [Card]) {
        self.deck = deck
        self.cards = cards
    }
    
    public var numberOfItems: Int {
        return cards.count
    }
    
    public func addNewItem(at index: Int) -> Hand {
        return insertCard(card: deck.nextCard(), at: index)
    }
    
    private func insertCard(card: Card, at index: Int) -> Hand {
        var mutableCards = cards
        mutableCards.insert(card, at: index)
        return Hand(deck: deck, cards: mutableCards)
    }
    
    public func deleteItem(at index: Int) -> Hand {
        var mutableCards = cards
        mutableCards.remove(at: index)
        return Hand(deck: deck, cards: mutableCards)
    }
    
    public func moveItem(fromIndex: Int, toIndex: Int) -> Hand {
        return deleteItem(at: fromIndex).insertCard(card: cards[fromIndex], at: toIndex)
    }
    
    subscript(index: Int) -> Card {
        return cards[index]
    }
}
ソースコード GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/ios/Hand - GitHub
関連情報 文化を調和させる
【Cocoa練習帳】 http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)

[Swifty]プロトコルを導入する

テーブルビュー関連のコードを切り離す。

SourceTypeというプロトコルを作り、プロトコルエクステンションとして中身を実装する。

protocol SourceType: UITableViewDataSource {
    func insertTopRowIn(tableView: UITableView)
    func deleteRowAtIndexPath(indexPath: NSIndexPath, from tableView: UITableView)
}
 
extension SourceType {
    func insertTopRowIn(tableView: UITableView) {
        let indexPath = IndexPath(row: 0, section: 0)
        tableView.insertRows(at: [indexPath], with: .fade)
    }
    
    func deleteRowAtIndexPath(indexPath: NSIndexPath, from tableView: UITableView) {
        tableView.deleteRows(at: [indexPath as IndexPath], with: .fade)
    }
}

データソースクラスに組み込む。

class DataSource: NSObject, UITableViewDataSource, SourceType {
    private var document = Document.sharedInstance
    
    func addItemTo(tableView: UITableView) {
        if document.numberOfCards < 5 {
            document.addNewCard(at: 0)
            insertTopRowIn(tableView: tableView)
        }
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return document.numberOfCards
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CardCell
        let card = document.getCard(at: indexPath.row)
        cell.fillWith(card: card)
        return cell
    }
    
    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == .delete {
            document.deleteCard(at: indexPath.row)
            tableView.deleteRows(at: [indexPath], with: .fade)
        } else if editingStyle == .insert {
            // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
        }
    }
}
ソースコード GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/ios/Hand - GitHub
関連情報 文化を調和させる
【Cocoa練習帳】 http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)

[Swifty]データソースを分割する

そもそもUITableViewはUITableViewDataSourceプロトコルとUITableViewDelegateプロトコルの二つのプロトコルを利用するということから、複数のクラスで更生できる物だが、見通しが良く、記述が楽ということから、ベースとなるビューコントローラで実装されている場合が多いと思う。

 

今回のデータソースに分割するお話は、クラス構成から素直に実装とも言えると思う。

 

まずは、データソースを切り離してみる。

 

class DataSource: NSObject, UITableViewDataSource {
    private var document = Document.sharedInstance
 
    func addItemTo(tableView: UITableView) {
        if document.numberOfCards < 5 {
            document.addNewCard(at: 0)
            insertTopRowIn(tableView: tableView)
        }
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return document.numberOfCards
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CardCell
        let card = document.getCard(at: indexPath.row)
        cell.fillWith(card: card)
        return cell
    }
    
    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == .delete {
            document.deleteCard(at: indexPath.row)
            tableView.deleteRows(at: [indexPath], with: .fade)
        } else if editingStyle == .insert {
            // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
        }
    }
    
    func insertTopRowIn(tableView: UITableView) {
        let indexPath = IndexPath(row: 0, section: 0)
        tableView.insertRows(at: [indexPath], with: .fade)
    }
    
    func deleteRowAtIndexPath(indexPath: NSIndexPath, from tableView: UITableView) {
    }
}

 

class MasterViewController: UITableViewController {
 
    private var dataSource = DataSource()
    var detailViewController: DetailViewController? = nil
 
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        tableView.dataSource = dataSource
        self.navigationItem.leftBarButtonItem = self.editButtonItem
 
        let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(MasterViewController.addNewCard(sender:)))
        self.navigationItem.rightBarButtonItem = addButton
        if let split = self.splitViewController {
            let controllers = split.viewControllers
            self.detailViewController = (controllers[controllers.count-1] as! UINavigationController).topViewController as? DetailViewController
        }
    }
 
    override func viewWillAppear(_ animated: Bool) {
        self.clearsSelectionOnViewWillAppear = self.splitViewController!.isCollapsed
        super.viewWillAppear(animated)
    }
 
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
 
    @IBAction private func addNewCard(sender: UIBarButtonItem) {
        dataSource.addItemTo(tableView: tableView)
    }
 
    // MARK: - Segues
 
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "showDetail" {
            if let indexPath = self.tableView.indexPathForSelectedRow {
                let card = Document.sharedInstance.getCard(at: indexPath.row)
                let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
                controller.detailItem = card
                controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem
                controller.navigationItem.leftItemsSupplementBackButton = true
            }
        }
    }
 
    // MARK: - Table View
 
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
 
    override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
        // Return false if you do not want the specified item to be editable.
        return true
    }
}

 

ソースコード

GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/ios/Hand - GitHub

 

関連情報

文化を調和させる


【Cocoa練習帳】

http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)

Cocoa Study at Ikebukuro #6 (BUKURO.swift)

12/5に開催された勉強会『Cocoa Study at Ikebukuro #6 (BUKURO.swift)』について報告する。

場所はいつもの池袋コワーキングスペース OpenOffice FOREST。池袋駅から少し離れた場所だ。座席代は夜間ドロップインで一人1,000円。一時、渋谷でも開催していたが、会場の都合から池袋での開催となったが、利用しやすいので助かっている。

発表内容を簡単にまとめると以下の通り。

# GNUstep用アプリ”MyWiki.app”をビルドする

構想しているアプリケーションの参考として発表者が探してきたのが、GNUstepのサンプルコード"MyWiki.app"。GNUstepだというのが渋い。そして、手を加えないといけなかったが、最新のmacOSでも動作。見た目は、iWebを思い出させる、ちょっと懐かしい感じのUI。OPENSTEPの流儀で画像データをコードで記述する方法。なるほどねでした。

# MacOSX用アプリ”CardBook.app”をビルドする

こちらも先ほどの発表者が見つけてきたソースコード。素朴なメモ帳という感じだ。印刷に対応というのが、今の時代、懐かしい!

# TouchBarDemoApp

新型MacBook Proで話題のTouchBar。これを非対応のMacintoshで利用できるようにするのが、このソースコード。その感じから、OS9が動作してPowerBookの思い出していしまいました。

# Swiftyを試す

Try! Swift 2016での発表『文化を調和させる: 関数型プログラミング、プロトコル志向プログラミング、オブジェクト指向プログラミングの優れたテクニックを取り入れる』の手法を実際に試してみた事の発表。ボリュームがあり、最後に到達できなかったが、都築は、次回、勉強会で、出そうだ。

今回は、macOSのプログラミングが発表が多かったが、iOSの時代。新鮮に感じ、かつ、面白い!これからはmacOS向けAppStoreが熱いかな!?

関連情報 Cocoa Study at Ikebukuro #6 (BUKURO.swift)
Cocoa Study
Cocoa勉強会 関東
Cocoa勉強会
Cocoa勉強会(関東)
【Cocoa練習帳】 http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)

[Swifty]カスタムセルを作る

今回のテーマは、少し取り上げにくい。なんとか頑張ってみる。

カスタムセルの実装をMVVM (Model - View - ViewModel)でと説明されているが、ここがスムーズにいかない。

MVVMは、Microsoftの.Netフレームワーク側のWPF(Windows Presentation Foundation)やSilverlightの設計方法で、WindowsのXAMLでViewを構成する場合にあったやり方。なので、異なるフレームワークのCocoaでもあるし、MVCやモジュール化、関数化の流れの物だと思うので、わざわざ、Cocoaの世界でMVVMというのは。

MVVM化として説明されているのが、セルに値を設定するメソッド。

class CardCell: UITableViewCell {
    @IBOutlet var titleLabel: UILabel?
 
    public func fillWith(card: Card) {
        titleLabel?.text = card.title
    }
}

セルの設定でビューコントローラのプロパティを使うのを避けたいという事のようだが、自分の場合は、Model(Dataコントローラ)を用意しているので、ちょっと状況が異なる。

これ以外に、自分の知らなかった、役立つ手法が紹介されていた。

先ずは、guard let。

guard 条件節 else { /* breakやreturn */ }
func demo() -> Cell {
    guard let cell = crateCell else {
        fatalError("Could not create cell")
    }
    cell.setTitle("表題")
    return cell
}

Eiffel言語の事前条件のような利用を想定しているようで、ブロックの頭で、条件に合わない場合は弾く処理を記述するようの構文のようだ。

次は、subscript。

class Hand {
    private var cards = [Card]()

    subscript(index: Int) -> Card {
        return cards[index]
    }
}
 
var card = Hand[0]

配列は辞書の添字の多重定義、といったところか。例の場合は分かりにくくなっているが、C言語のマクロで簡素化していたコードなどをこれで置き換えることができそうだ。

ソースコード GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/ios/Hand - GitHub
関連情報 文化を調和させる
【Cocoa練習帳】 http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)

[Swifty]mutatingを取り除く

ClassからStructに変更すると、プロパティを変更するメソッドにはmutatingキーワードをつけなければいけない。参考にしている発表では、これが格好悪いのでつける必要がない方法を選択したい。それが関数型プログラミング。SwiftではCopy-On-Writeに実装されていものがあるので、コストを気にしなくてもという事のようだ。

struct Deck {
    public func nextCard() -> Card {
        let card = Card()
        return card
    }
}
 
struct Hand {
    private let deck = Deck()
    private var cards = [Card]()
    
    public init(deck: Deck, cards: [Card]) {
        self.deck = deck
        self.cards = cards
    }
}

Handのプロパティcardsにカードを追加する場合、プロパティに変更を加えるメソッドを用意するのではなくて、内容をコピーして、カードを追加されたハンドを生成して返す。それとハンドを差し替えればいいじゃないの、という考えだ。

    private func insertCard(card: Card, at index: Int) -> Hand {
        var mutableCards = cards
        mutableCards.insert(card, at: index)
        return Hand(deck: deck, cards: mutableCards)
    }

これを利用すれば、新規カードの追加はこうなる。

    public func addNewCard(at index: Int) -> Hand {
        return insertCard(card: deck.nextCard(), at: index)
    }

カードの削除はこうだ。

    public func deleteCard(at index: Int) -> Hand {
        var mutableCards = cards
        mutableCards.remove(at: index)
        return Hand(deck: deck, cards: mutableCards)
    }

これらを組み合わせれば、カードの移動もこうなる。

    public func moveCard(fromIndex: Int, toIndex: Int) -> Hand {
        return deleteCard(at: fromIndex).insertCard(card: cards[fromIndex], at: toIndex)
    }

単なる配列操作だが、美しさを取るか!君はどっちだ!

ソースコード GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/ios/Hand - GitHub
関連情報 文化を調和させる
【Cocoa練習帳】 http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)