RubyMotion 用 gem の Teacup は、CSS っぽくビューのデザインを記述できる、 厳密にはビューのプロパティを設定するための内部 DSL。 なんと AutoLayout もサポートしていたりする。
Teacup の AutoLayout を使って、カスタム UITableViewCell をレイアウトしようとして、 README に書かれていないことで結構苦労したのでメモしておく。
まずスタイルを記述する。
Teacup::Stylesheet.new :item_cell do style :title_label, constraints: [ constrain(:left).equals(:superview, :left), constrain(:top).equals(:superview, :top), constrain(:bottom).equals(:superview, :bottom), ] style :price_label, constraints: [ constrain(:right).equals(:superview, :right), constrain(:top).equals(:superview, :top), constrain(:bottom).equals(:superview, :bottom), ] style :comment_label, constraints: [ constrain(:left).equals(:title_label, :right), constrain(:right).equals(:price_label, :left), constrain(:top).equals(:superview, :top), constrain(:bottom).equals(:superview, :bottom), ] end
constraints の部分が AutoLayout。ここ記述ミスしやすい上に、ミスっていてもなかなか気づかない。今回は1時間ほどハマった。要注意。
次にスタイルを適用するセルを作る。 UIViewController は Teacup が拡張していて stylesheet メソッドが使えるけど、 UIView は拡張されていない。 Teacup::Layout を incude する必要がある。
class ItemCell < UITableViewCell CELL_ID = "item-cell" include Teacup::Layout stylesheet :item_cell def self.cellForItem(item, inTableView:tableView) cell = tableView.dequeueReusableCellWithIdentifier(CELL_ID) unless cell cell = self.alloc.initWithStyle( UITableViewCellStyleDefault, reuseIdentifier:CELL_ID ) end cell.setupItem(item) cell end def initWithStyle(style, reuseIdentifier:reuseIdentifier) super layout self.contentView do @titleLabel = subview(UILabel, :title_label) @commentLabel = subview(UILabel, :comment_label) @priceLabel = subview(UILabel, :price_label) end self end def setupItem(item) @titleLabel.text = item[:title] @commentLabel.text = item[:comment] @priceLabel.text = "#{item[:price]} 円" end end
セルを使うコントローラー。
class MainViewController < UITableViewController def self.controllerWithNavigation UINavigationController.alloc.initWithRootViewController(self.alloc.init) end def init initWithStyle(UITableViewStylePlain) @items = [ { title: "foo", comment: "aaaaa", price: 100 }, { title: "bar", comment: "bbbbb", price: 200 }, { title: "hoge", comment: "ccccc", price: 300 }, { title: "fuga", comment: "eeeee", price: 400 }, ] self end def viewDidLoad super self.title = "Sample" end def tableView(tableView, numberOfRowsInSection:section) @items.size end def tableView(tableView, cellForRowAtIndexPath:indexPath) item = @items[indexPath.row] cell = ItemCell.cellForItem(item, inTableView:tableView) # AutoLayout 適用 cell.restyle! cell.apply_constraints cell end end
cell の restyle! と apply_constrains を呼び出さないと、スタイルに記述した制約が適用されない。 README に書いてなくて、これまたハマった。最終的にはソースコードまで読むことに。
最後に、今回作成したサンプルを載せておく。
# coding: utf-8 Teacup::Stylesheet.new :item_cell do style :title_label, constraints: [ constrain(:left).equals(:superview, :left), constrain(:top).equals(:superview, :top), constrain(:bottom).equals(:superview, :bottom), ] style :price_label, constraints: [ constrain(:right).equals(:superview, :right), constrain(:top).equals(:superview, :top), constrain(:bottom).equals(:superview, :bottom), ] style :comment_label, constraints: [ constrain(:left).equals(:title_label, :right), constrain(:right).equals(:price_label, :left), constrain(:top).equals(:superview, :top), constrain(:bottom).equals(:superview, :bottom), ] end class MainViewController < UITableViewController def self.controllerWithNavigation UINavigationController.alloc.initWithRootViewController(self.alloc.init) end def init initWithStyle(UITableViewStylePlain) @items = [ { title: "foo", comment: "aaaaa", price: 100 }, { title: "bar", comment: "bbbbb", price: 200 }, { title: "hoge", comment: "ccccc", price: 300 }, { title: "fuga", comment: "eeeee", price: 400 }, ] self end def viewDidLoad super self.title = "Sample" end def tableView(tableView, numberOfRowsInSection:section) @items.size end def tableView(tableView, cellForRowAtIndexPath:indexPath) item = @items[indexPath.row] cell = ItemCell.cellForItem(item, inTableView:tableView) # AutoLayout 適用 cell.restyle! cell.apply_constraints cell end end class ItemCell < UITableViewCell CELL_ID = "item-cell" include Teacup::Layout stylesheet :item_cell def self.cellForItem(item, inTableView:tableView) cell = tableView.dequeueReusableCellWithIdentifier(CELL_ID) unless cell cell = self.alloc.initWithStyle( UITableViewCellStyleDefault, reuseIdentifier:CELL_ID ) end cell.setupItem(item) cell end def initWithStyle(style, reuseIdentifier:reuseIdentifier) super layout self.contentView do @titleLabel = subview(UILabel, :title_label) @commentLabel = subview(UILabel, :comment_label) @priceLabel = subview(UILabel, :price_label) end self end def setupItem(item) @titleLabel.text = item[:title] @commentLabel.text = item[:comment] @priceLabel.text = "#{item[:price]} 円" end end class AppDelegate def application(application, didFinishLaunchingWithOptions:launchOptions) @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds) @window.rootViewController = MainViewController.controllerWithNavigation @window.makeKeyAndVisible true end end