2017年9月17日日曜日

Objective-CのコードをSwiftに移植

Swift3にもなって、そろそろObjective-Cで作ったアプリをSwift化した方がいいと思うようになった。

メンテナンスのたびにObjective-Cを思い出しながら作業するのも面倒。
いつアップルが「もうObjective-Cのサポートやめるから、これからは審査通さんからね」って言い出しかねない。
なんだかんだ言ってObjective-Cのコードはごちゃごちゃしてることが多い。ヘッダファイルやimportも必要だし。

そんなわけで今さらだが、Swiftに移植してみることにした。

以下、調べたことのメモ

  • 同じプロジェクト内でクラスのファイル(ViewController.h、同.mとか)ごとに移行可能。
  • クラスのファイル名は拡張子が別になるので、Objective-CとSwiftで同居可能。(ViewController.h、同.m、同.swift)
  • 最初にSwiftファイルを追加するときに「アプリ名-Bridging-Header.h」というファイルを作るかどうか聞かれるので、作っておく。

手順

移行するクラスファイルと同名のSwiftファイルを作ってコードを移植。
Objective-Cの.hと.mのファイルを削除。
Storyboardに結びついてるならCustom ClassのModuleがNoneになってるので、そこをプロジェクト名に直す。(最初にCurrentに直すとも?)
IBOutletやIBActionの結びつきを直す。

Objective-C、Swift相互の呼び出し

Objective-CのクラスからSwiftのクラスを呼ぶ

Objective-CのクラスからSwiftのクラスを呼ぶには、呼び出し元のObjective-Cの実装ファイル(.m)に
#import "アプリ名-Swift.h"
を追加。
たとえばアプリ名がMyAppだったら
#import "MyApp-Swift.h"
とする。

ただ、なんかうまく認識してくれないことがあって、よくわかんねえんだ…。(´・ω・`*)
importしたら、すぐにそのクラスを認識してコードのサジェスト(推薦候補)を表示してくれるようになるわけじゃないようで、一度ビルドかけるとようやく認識してくれたり。それでも認識されなかったり。
このあたりはもっとしっかり作ってくれるといいんだけどね、Xcode。どうせSwiftへ移行する過渡期だけの仕様だから、いい加減なのかねえ?

SwiftからObjective-Cを呼ぶ

逆にSwiftからObjective-Cを呼ぶには作っておいた「プロジェクト名-Bridging-Header.h」に呼び出し先のObjective-Cファイルをimportする。

でいいらしい。

AppDelegateの移植

AppDelegateも同様に移植する。

main.mの削除

Objective-CではSupportingFilesフォルダ中にmain.mが作られてるけど、これが残ってるとかえってエラーになるので、削除する。

@UIApplicationMain

Swiftの普通のAppDelegateを参考にすりゃわかるけど、class AppDelegateの前に@UIApplicationMainってのを書いてやる。
多分これで最初に呼ばれるファイルに指定されるんだろう。

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

2017年9月14日木曜日

TableViewを使う


Xcode右下の部品一覧からUITableViewControllerをStoryboardにドロップすると簡単なんだけど、なぜかTableViewにConstraintsが設定できない。
一番上のCellがステータスバーに重なってしまうなどよろしくない。

普通のUIViewControllerにUITableViewをドロップし、UIViewの子Viewにすればこれは回避できる。
その際はTableViewとViewControllerをdelegateとdataSourceで結んでやること。(Storyboardの黄色いアイコンまで右ボタンドラッグ)

TableViewのStoryboard IDとか、TableViewCellのRestoration IDとかも設定した方がいい。

また、ViewControllerに結びつけるSwiftのクラスはUIViewControllerを親クラスとし、UITableViewDelegateと同DataSourceを設定してやる。

class TableViewController: UIViewController, UITableViewDelegate,UITableViewDataSource {
}

さらに、delegateメソッドはoverrideにしない。

2017年9月12日火曜日

乱数の関数メモ

いろいろある乱数発生の関数をメモ

arc4random()
返り値の型はUInt32なので、Swiftみたいに型をきっちり指定してやらないといけない言語の場合、ちょい面倒。

arc4random_uniform(__upper_bound: UInt32)
0〜引数-1までのランダムな整数を返す。
arc4random_uniform(10)なら0〜9まで。使い勝手良さげ。


drand48()
返り値の型はDoubleらしい。
0.0〜1.0の間の負でない倍精度の浮動小数点値を返す。
UIColorのrgb値が0.0〜1.0とこれにドンピシャガンガンなので、いちいちarc4randomとか使わんでいい。
そのかわりUIColor(red: , green: , blue: , alpha: )の引数がCGFloatなので、
let fontColor = UIColor(red: CGFloat(drand48()), green: CGFloat(drand48()), blue: CGFloat(drand48()), alpha: 1.0)
とかやらんといかんけどね。
今知ったばかりなんでよくわかってないw

UICollectionViewを使う際の注意

つまった点のメモ。
右クリックのドラッグで黄色のアイコンと結んでやる
StoryboardでUICollectionViewの部品をUIViewControllerの上にドロップするのはいい。
その後、ドロップしたそれをマウス右ボタンクリックでそのStoryboardの上にある黄色いアイコンにドラッグし、ポップアップしたメニューからdataSourceを選んで接続してやる必要がある。
これに気づかずに「コード合ってんのに表示されねー」ってだいぶ悩んだ。

あとはUICollectionViewCellのReusableViewのIDの設定とか、protocolになってる
UICollectionViewDelegate, UICollectionViewDataSource
をclassのところで設定してやるとか、その辺がつまづきやすい。

ところで、UIViewControllerにUICollectionViewを貼るよりも、XcodeのUI部品一覧の中にUICollectionViewControllerってのがあるから、それをStoryboardにドラッグした方がdataSourceとdelegateの設定もされるから早いみたいね ^^;

2017年9月6日水曜日

StoryboardでScrollViewのコンテンツを配置

UIScrollView上に配置するUIをStoryboardで配置しようとすると、iPhoneやiPadのサイズからはみ出したUIが隠れてしまう。
ScrollViewの上にMapViewと紫色のViewが乗ってる

そこで、そのStoryboardのViewControllerを選択し、SizeInspectorのSimulated SizeをFreeformにし、Height(もしくはWidth)を必要な値に変更してやれば、Storyboardがその長さまで広がってくれるのでUIの配置が簡単にできるようになる。


MapViewに表示できるpinの数

1000個くらい一度に表示できないものか、できるなら表示速度はどんなもんじゃろうと試してみた。

横並びの制限?

経度1度ごとに横に並べるようにしたら、なぜか411個という中途半端な個数が最大だった。
なぜか411個が限界

ランダム配置なら制限なし?

しかし世界中のランダムな位置に刺すようにしたところ、1000個でも表示できた。
1000個でもいけた
昔のパソコンのスプライトじゃあるまいし、横位置に並べられる制限があるのかしら?

なお表示速度はどちらも一瞬で、目立った遅延もないようなので気にしなくて良さそう。

10,000個でも大丈夫

10,000個にもチャレンジしたら、表示できたが、さすがにspanを広くしてpinがいっぱい表示されるようにしたら重くなった。(iPad Air2)
でもちょっと引っかかる程度で、実用上さほど問題あるとも思えなかった。spanを狭く取ればスイスイだし。
10,000個もpinを表示する機会は滅多にないよね。
10,000個でもいけたがちょっと重い


override func viewDidLoad() {
        super.viewDidLoad()
        
        //pinを1000個追加
        var annotationArr = Array<MKAnnotation>()
        for i in 1...1000 {
            let anno = MKPointAnnotation()
            let longi = Double(arc4random() % UInt32(360)) - Double(180)
            let lat   = Double(arc4random() % UInt32(180)) - Double(90)
            print(i)
            anno.coordinate = CLLocationCoordinate2DMake(Double(lat), Double(longi))
            anno.title = "経度:\(longi) 緯度:\(lat)"
            anno.subtitle = "番号 \(i)"
            annotationArr.append(anno)
        }
        self.myMapView.addAnnotations(annotationArr)

        //1000番目のpinの位置をマップのデフォルト位置に
        let lat = annotationArr.last?.coordinate.latitude
        let lng = annotationArr.last?.coordinate.longitude
        let coordinate = CLLocationCoordinate2DMake(lat!, lng!)
        let span = MKCoordinateSpanMake(0.05, 0.05) //表示範囲(拡大)
        let region = MKCoordinateRegionMake(coordinate, span)
        self.myMapView.setRegion(region, animated: true)

    }


ちなみに以前勉強した時はMKPointAnnotationを使わずにカスタムクラスを作ってannotationを管理するようにって読んだんだけど、単純に表示するだけならMKPointAnnotationで事足りるよね? よくわかんないけどいいか。

色を変えてみる

1000個表示でも少し重い
ピンの色を変えるにはMapViewのdelegateメソッドを使う必要がある。

ピンの緯度経度、タイトル/サブタイトル、色やアイコンのデザインまで一括してプロパティに持つクラスがありゃいいのにと思うけど、Appleの技術者様の考えることはわからない。
しょうがないので、みんなカスタムクラスを作ってるみたい。

表示は少し重くなる。ピンを表示するごとにdelegateメソッドが呼ばれるせいだろう。
数が少なければ気にならないと思う。


//pinを1000個追加の後あたりに以下を追加
self.myMapView.delegate = self

さらにメソッドを追加。
    //pinのデザインを変えるにはdelegateメソッドを使う
    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
        let annoView = MKPinAnnotationView()
        annoView.annotation = annotation
        let r = Float(Double(arc4random() % 10) / 10)
        let g = Float(Double(arc4random() % 10) / 10)
        let b = Float(Double(arc4random() % 10) / 10)
        annoView.pinTintColor = UIColor.init(colorLiteralRed: r, green: g, blue: b, alpha: 1.0)
        annoView.canShowCallout = true
        
        return annoView
    }

画像を変えてみる

画像2種類+色を変えたピン
意外と軽い
今度は画像を変更して見たところ、動きが軽い。
色を変えないピンよりは引っかかるが、色を様々に変えたものよりは軽いのだ。
delegateメソッドを繰り返し呼ぶのがボトルネックになっているのではなく、新たに色が違う画像を生成するか、キャッシュされた画像を再利用するかの点で違いが出てるのかも?
もっと画像の種類を増やすと違いが出てくるだろう。

なお、画像の中心(9*9の画像なら5,5の位置)が設定した緯度経度の位置になるようだ。
同じ緯度経度のデフォルトのピンと重ねてみるとよくわかる。

2017年9月3日日曜日

ARとモーションセンサー覚書

勉強中のARプログラミングと、CMMotionManager( )を使ったモーションセンサー等についてメモっとく。

ジンバルロック

詳しくはググってほしいけど、3軸の姿勢センサーの2軸の動きが重なってしまい、姿勢が正しく計測できなくなること。
姿勢センサーを使ったアプリで、時々「∞の字に動かしてください」とか出るのはこれをリセットするためか?

Pitch, Roll, Yaw値の範囲

CMMotionManager( )のAttitudeとして得られるそれぞれの値の範囲(度数法の角度で)
  • Pitch
    • -90 〜 0 〜 90
    • デバイスを机に画面を上で寝かせた状態が0
    • 垂直に起こした状態が90
    • デバイス上部を垂直に下にした状態が-90
  • Roll
    • -180 〜 0 〜 180
    • デバイスを机に画面を上で寝かせた状態が0
    • 左に傾けて起こしていくとマイナスになって、完全にひっくり返すと-180
    • 右に傾けて起こしていくとプラスになって、完全にひっくり返すと180
  • Yaw
    • -180 〜 0 〜 180
    • デバイスを机に画面を上で寝かせて西を向けた状態が0
    • 反時計回りでプラスになって、東で180
    • 時計回りでマイナスになって、東で-180