読者です 読者をやめる 読者になる 読者になる

TitaniumMobile勉強記

Web系エンジニア向けのキャリアアドバイザーやってましたが現在はフリーランスで開発含めて色々やってます。技術ネタとしてはRuby/RailsとJavaScript関連(Node.js、Titanium)あたり

TableViewでアコーディオンメニューのようなものを実装したがあまり実用的でなかった

本題に入る前に近況など

最近、ブログ更新頻度落ちてるのですが、主に以下3つが要因かなと思ってます

  • クラフトビールのお店情報を検索閲覧できるiPhone/Androidアプリを優先的(*)に作業してる
  • 知り合いに頼まれて本業に関連するキャリア関連のお話を30分弱したり、昨日の勉強会でこんなLTしたり、明後日の勉強会でLTする予定
  • Titanium Mobile 使ったスマフォアプリのワークショップを不定期に開催しており、そこでも使えるようにKDP向けにTitaniumの書籍原稿執筆中

ブログ書きたい内容は結構あるんだけど、当面はiPhone/Androidアプリ開発に注力してそれが一段落したら、もう少し更新頻度あげていこうかなと思ってます

(*) 今回はデザインも自分でやる方針にして、アイコンをSketch 2 & Pixelmator使って頑張って作ってみました

f:id:h5y1m141:20130612074003p:plain

ここから本題

こんな↓感じのものを作ったのですが、このエントリのタイトルそのままで動作がカクカクして正直実用的でないかなと思ってます

実装にあたって

あらかじめ、

var prefectures;
prefectures = [
  {"name":"北海道","area":"北海道・東北"},
  {"name":"青森県","area":"北海道・東北"},
  {"name":"岩手県","area":"北海道・東北"},
  {"name":"宮城県","area":"北海道・東北"},
  {"name":"秋田県","area":"北海道・東北"},
  {"name":"山形県","area":"北海道・東北"},
  {"name":"福島県","area":"北海道・東北"},
  {"name":"茨城県","area":"関東"},
  {"name":"栃木県","area":"関東"}
  // 以下省略

みたいな都道府県のエリアと、都道府県名を紐付けたデータを準備しました。

そして、このデータを元にして、

"北海道・東北":[
  {"name":"北海道","area":"北海道・東北"}
  {"name":"青森県","area":"北海道・東北"}
],
"関東":[
  {"name":"茨城県","area":"関東"},
  {"name":"栃木県","area":"関東"}
]

みたいな構造のものを生成するためにUnderscore.jsのgroupBy()を以下のように使うことにします

  _makePrefectureCategory: (data) ->
    _ =  require("lib/underscore-1.4.3.min")
    result = _.groupBy(data,(row) ->
      return row.area
    )
    return result

あとは、都道府県のエリア名、つまり、「北海道・東北」「関東」・・・という項目のものだけをTableViewで表示しておき、項目がタッチされた時に、タッチされた行の下にそのエリアに紐づく都道府県名を1つづつ追加しています

  _showSubMenu:(prefectureNameList,curretRowIndex) ->
    index = curretRowIndex
    for item in prefectureNameList
      subMenu = Ti.UI.createTableViewRow
        width:'auto'
        height:40
        borderWidth:0
        className:'subMenu'
        prefectureName:item.name
        backgroundGradient: 
          type: 'linear'
          startPoint: 
            x: '0%',
            y: '0%'
          ,
          endPoint: 
            x: '0%'
            y: '100%'
          ,
          colors: @colorSet
      subMenuLabel = Ti.UI.createLabel
        width:240
        height:40
        top:5
        left:30
        color:'#222'
        font:
          fontSize:14
          fontWeight:'bold'
        text:item.name
      subMenu.add subMenuLabel
      @table.insertRowAfter(index,subMenu,{animated:false})
      @_sleep(100)
      index++
      Ti.API.info "index is #{index}"
      # Ti.API.info item.name
  
    return

という感じで、それに紐づく都道府県名を、insertRowAfter()使って、1つづつ動的に追加しています。

insertRowAfter()を使うアプローチだと限界がある気がする

TableView insertRow animation - change durationという記事を昨日たまたま見つけて、

it seems to be set to 200 ms.

という感じで、rowの挿入するアニメーションで200msの時間が設定されているっぽいし、実際にっ自分が書いてても、感覚的にはこれ位の時間がかかってるように感じます。

カクカクする理由の1つに、insertRowした後に、独自に作ったsleep()で処理を止めているのですが、実はこの処理を入れないと、再現性がないのですが、

[ERROR] no row found for index.

という時がありinsertRowの処理が追いつかないのかと思い、苦肉の策でsleep()を入れた所、今度は動作がカクカクするという状況なので、insertRowAfter()を使うアプローチだと限界あるのかなと思ってます。

@ryugoo_さんに昨日あった時に、insertRowAfter/Beforeは避けたほうがいいとアドバイスもらって、やっぱりTableView.setData()の方がいいのかと思ってます

折角作ったのと、将来気が向いて手直しするかもしれなので、コード以下に貼っておくことにします。

## shopDataTableView.coffee

class shopDataTableView
  constructor: () ->
    
    prefectures = [
      {"name":"北海道","area":"北海道・東北"},
      {"name":"青森県","area":"北海道・東北"},
      {"name":"岩手県","area":"北海道・東北"},
      {"name":"宮城県","area":"北海道・東北"},
      {"name":"秋田県","area":"北海道・東北"},
      {"name":"山形県","area":"北海道・東北"},
      {"name":"福島県","area":"北海道・東北"},
      {"name":"茨城県","area":"関東"},
      {"name":"栃木県","area":"関東"},
      {"name":"群馬県","area":"関東"},
      {"name":"埼玉県","area":"関東"},
      {"name":"千葉県","area":"関東"},
      {"name":"東京都","area":"関東"},
      {"name":"神奈川県","area":"関東"},
      {"name":"新潟県","area":"中部"},
      {"name":"富山県","area":"中部"},
      {"name":"石川県","area":"中部"},
      {"name":"福井県","area":"中部"},
      {"name":"山梨県","area":"中部"},
      {"name":"長野県","area":"中部"},
      {"name":"岐阜県","area":"中部"},
      {"name":"静岡県","area":"中部"},
      {"name":"愛知県","area":"中部"},
      {"name":"三重県","area":"近畿"},
      {"name":"滋賀県","area":"近畿"},
      {"name":"京都府","area":"近畿"},
      {"name":"大阪府","area":"近畿"},
      {"name":"兵庫県","area":"近畿"},
      {"name":"奈良県","area":"近畿"},
      {"name":"和歌山県","area":"近畿"},
      {"name":"鳥取県","area":"中国・四国"},
      {"name":"島根県","area":"中国・四国"},
      {"name":"岡山県","area":"中国・四国"},
      {"name":"広島県","area":"中国・四国"},
      {"name":"山口県","area":"中国・四国"},
      {"name":"徳島県","area":"中国・四国"},
      {"name":"香川県","area":"中国・四国"},
      {"name":"愛媛県","area":"中国・四国"},
      {"name":"高知県","area":"中国・四国"},
      {"name":"福岡県","area":"九州・沖縄"},
      {"name":"佐賀県","area":"九州・沖縄"},
      {"name":"長崎県","area":"九州・沖縄"},
      {"name":"熊本県","area":"九州・沖縄"},
      {"name":"大分県","area":"九州・沖縄"},
      {"name":"宮崎県","area":"九州・沖縄"},
      {"name":"鹿児島県","area":"九州・沖縄"},
      {"name":"沖縄県","area":"九州・沖縄"}      
    ]
    @table = Ti.UI.createTableView
      backgroundColor:'#fff'
      separatorColor: '#ccc'
      width:'auto'
      height:'auto'
      left:0
      top:0
      
    @colorSet = [
      color: "#fff"
      position: 0.0
    ,
      color: "#eee"
      position: 0.3
    ,
      color: "#ededed"
      position: 1.0
    ]      
    
    
    @table.addEventListener('click',(e) =>
      that = @
      opendFlg = e.row.opendFlg
      prefectureNameList = e.row.prefectureNameList
      curretRowIndex = e.index
      if opendFlg is false
        @_showSubMenu(prefectureNameList,curretRowIndex)
          
        e.row.opendFlg = true          
      else if opendFlg is true
        @_hideSubMenu(curretRowIndex,prefectureNameList.length)
        e.row.opendFlg = false
      else
        Ti.API.info e.row.prefectureName
(e))
        return
    )
    rows = []
    PrefectureCategory = @_makePrefectureCategory prefectures
     
    for categoryName of PrefectureCategory
      numberOfPrefecture = PrefectureCategory[categoryName].length
      prefectureNameList = PrefectureCategory[categoryName]  
      
      textLabel = Ti.UI.createLabel
        width:240
        height:40
        top:5
        left:5
        color:'#222'
        font:
          fontSize:18
          fontWeight:'bold'
        text:"#{categoryName}"

      if Titanium.Platform.osname is "iphone"
        row = Ti.UI.createTableViewRow
          width:'auto'
          height:40
          borderWidth:0
          className:'shopData'
          numberOfPrefecture:numberOfPrefecture
          prefectureNameList:prefectureNameList
          opendFlg:false
          backgroundGradient: 
            type: 'linear'
            startPoint: 
              x: '0%',
              y: '0%'
            ,
            endPoint: 
              x: '0%'
              y: '100%'
            ,
            colors: @colorSet
        row.add textLabel
      else if Titanium.Platform.osname is "android"
        row = Ti.UI.createTableViewRow
          width:'auto'
          height:80
          className:'shopData'
          numberOfPrefecture:numberOfPrefecture
          prefectureNameList:prefectureNameList
          opendFlg:false
          
        view = Ti.UI.createView
          width:'auto'
          height:80
          backgroundGradient: 
            type: 'linear'
            startPoint: 
              x: '0%',
              y: '0%'
            ,
            endPoint: 
              x: '0%'
              y: '100%'
            ,
            colors: @colorSet
        view.add textLabel   
        row.add view
      else
        Ti.API.info 'no data'
      
      rows.push row
      
    @table.setData rows
    
    return @table

  # 都道府県のリスト情報から日本の地域x都道府県名の以下の様なリストを作成する
  # "北海道・東北":[ {},{} ],
  # "関東":[{},]
  # :

  _makePrefectureCategory: (data) ->
    _ =  require("lib/underscore-1.4.3.min")
    result = _.groupBy(data,(row) ->
      return row.area
    )
    return result
  _showSubMenu:(prefectureNameList,curretRowIndex) ->
    
    index = curretRowIndex
    Ti.API.info "curretRowIndex is #{curretRowIndex} and #{prefectureNameList.length}"

      
    for item in prefectureNameList
      subMenu = Ti.UI.createTableViewRow
        width:'auto'
        height:40
        borderWidth:0
        className:'subMenu'
        prefectureName:item.name
        backgroundGradient: 
          type: 'linear'
          startPoint: 
            x: '0%',
            y: '0%'
          ,
          endPoint: 
            x: '0%'
            y: '100%'
          ,
          colors: @colorSet

      subMenuLabel = Ti.UI.createLabel
        width:240
        height:40
        top:5
        left:30
        color:'#222'
        font:
          fontSize:14
          fontWeight:'bold'
        text:item.name
      subMenu.add subMenuLabel
      @table.insertRowAfter(index,subMenu,{animated:false})
      @_sleep(100)
      index++
      Ti.API.info "index is #{index}"
      # Ti.API.info item.name
  
    return
    
  _hideSubMenu:(curretRowIndex,numberOfPrefecture) =>
    if curretRowIndex is 0
      startPosition = numberOfPrefecture
    else
      startPosition = numberOfPrefecture + curretRowIndex
      
    endPosition = curretRowIndex+1
    Ti.API.info "start is #{startPosition} and end is  #{endPosition}"
    for counter in [startPosition..endPosition]
      @table.deleteRow counter
      @_sleep(100)


    return
    
  # 以下URLを参照してビジーループというアプローチでsleepを実装
  # http://yanor.net/wiki/?JavaScript%2F%E3%82%BF%E3%82%A4%E3%83%9E%E3%83%BC%E5%87%A6%E7%90%86%2Fsleep%E3%81%84%E3%82%8D%E3%81%84%E3%82%8D    
  _sleep:(time) ->
    d1 = new Date().getTime()
    d2 = new Date().getTime()
    d2 = new Date().getTime()  while d2 < d1 + time
    return
    
  _createShopDataRow:(placeData) ->
    titleLabel = Ti.UI.createLabel
      width:240
      height:20
      top:5
      left:5
      color:'#222'
      font:
        fontSize:16
        fontWeight:'bold'
      text:"#{placeData.name}"
      
    addressLabel = Ti.UI.createLabel
      width:240
      height:20
      top:25
      left:20
      color:'#444'
      font:
        fontSize:12
      text:"#{placeData.address}"

    if Titanium.Platform.osname is "iphone"
      row = Ti.UI.createTableViewRow
        width:'auto'
        height:45
        borderWidth:0
        hasChild:true
        placeData:placeData
        className:'shopData'
        backgroundGradient: 
          type: 'linear'
          startPoint: 
            x: '0%',
            y: '0%'
          ,
          endPoint: 
            x: '0%'
            y: '100%'
          ,
          colors: @colorSet
      row.add titleLabel
      row.add addressLabel
    else if Titanium.Platform.osname is "android"
      row = Ti.UI.createTableViewRow
        width:'auto'
        height:80
        className:'shopData'
        hasDetail:true
        
      view = Ti.UI.createView
        width:'auto'
        height:80
        backgroundGradient: 
          type: 'linear'
          startPoint: 
            x: '0%',
            y: '0%'
          ,
          endPoint: 
            x: '0%'
            y: '100%'
          ,
          colors: @colorSet
      view.add textLabel   
      row.add view    
    else
      Ti.API.info 'no platform'
    return row
        
module.exports = shopDataTableView