カテゴリー別アーカイブ: Swift

【Swift4】縦長のスクロール画面をscrollViewとstackViewを使って作る。

設定画面とか縦長でスクロールする画面を作りたい時ってあるじゃないですか。
でもテーブルビュー使うのめんどくさいな、みたいな。
そんなときに便利なやり方です。

一応の完成形はこんな感じ。
【Swift4】縦長のスクロール画面をscrollViewとstackViewを使って作る。

参考サイト
制約の修正なしで縦方向に要素を追加可能なビューの作成 – Qiita
【iOS】ちょっと待って!その画面UITableViewで作る必要ないかも – ペンギン村 Tech Blog
【UIScrollView】Autolayoutで縦スクロール【Xcode8.x】 – Qiita

ではまず、Safe AreaにScroll Viewを配置します。
Add New Constraintsで上下左右全てのマージンを0とします。
【Swift4】縦長のスクロール画面をscrollViewとstackViewを使って作る。

次いで、その中にVertical Stack Viewを配置し、Scroll Viewと同様に、Add New Constraintsで上下左右全てのマージンを0とします。
【Swift4】縦長のスクロール画面をscrollViewとstackViewを使って作る。

このままではScroll Viewのコンテントサイズの制約がないためエラーがでます。
【Swift4】縦長のスクロール画面をscrollViewとstackViewを使って作る。

マウスの右ボタンでStack ViewからScroll ViewへドラッグしてEqual Widthで横幅を揃える制限をつけます。
【Swift4】縦長のスクロール画面をscrollViewとstackViewを使って作る。

Stack Viewの子コンテンツでScroll Viewの高さが決まります。
とりあえずStack ViewのDistributionをEqual SpacingにしてSpacingを0にします。
【Swift4】縦長のスクロール画面をscrollViewとstackViewを使って作る。

子コンテンツを入れて高さが決まるとScroll Viewの高さが決まります。
【Swift4】縦長のスクロール画面をscrollViewとstackViewを使って作る。

さて、ここから縦長な画面にします。
操作するViewを選んで、simulated sizeを「freeform」にして、heightを設定します。
とりあえず1200にしてみました。
【Swift4】縦長のスクロール画面をscrollViewとstackViewを使って作る。

StackViewに高さが200の子Viewを6個入れて分かりやすように色を付けてみました。
【Swift4】縦長のスクロール画面をscrollViewとstackViewを使って作る。

これでアプリを起動すると綺麗にスクロールしました。\(^o^)/
【Swift4】縦長のスクロール画面をscrollViewとstackViewを使って作る。

これだけのことがコードを一行も書かずにできるってすごいなーと思います。\(^o^)/

【Swift4】NSDictionaryを要素に持つNSMutableArrayをDictionaryのKey順にソート

自分は独学でiPhoneアプリを作っています。
初心者向けのSwiftの本を数冊とあとはインターネットで検索して調べています。
C言語の基礎知識とかはまったくないです。英語のApple Developerなんちゃらを読んでもさっぱり理解できません。
しかし、やりたいことが決まって大体のやりかたはネットで調べれば基本的なことはたいてい出てきますが、
たまにこの人どーやって調べてこのやり方にたどり着いたんだろうと思うことがたまにあります。
今回もそんな感じです。
そして自分はすぐに忘れてしまうし、先人のブログが消えるとわからなくなるのでここに劣化コピーを載せています。

今回は、配列に入ったディクショナリをその要素でソートしたかったんですが、なかなかやり方が分からず難儀しました。
今回の参考サイトはこちら。
[swift] NSDictionaryを要素に持つNSArray/NSMutableArrayをKey順にソート | BlueBear I/O

適当に調べながらプレイグラウンドでコードを書いたので冗長だったりするかも。
サンプルコード

ほんとこーゆーのってどうやってしらべてるんだろう?
とりあえずなんとかなってよかった。
うじゃうじゃ。

【Swift4】機器がiPhoneかiPadかを判定。画面サイズでも判定。

参考サイト
【Swift4】iPhone・iPadなどの機種判定し処理を振り分ける方法【iOS9】 | ニートに憧れるプログラム日記

上記ページでもSwift4と書かれているが自分の環境ではうまく行かなかったので、試行錯誤した。
UIDevice.current.model で取得できるようだ。
あと、appleTVとcarPlayの場合何を返すのか確定できなかったので割愛した。

更に画面サイズで、細かいサイズ判定ができる。
画面が縦向きか横向きかで高さが変わるのでそれも考慮。

機種も変数で返してくれたら良いのになー。
うじゃうじゃ。

追記。
google Analytictから吐き出されるログを見てたら

“&dm” = “iPhone10,3″;
“&ds” = app;
“&sr” = 1125×2436;
“&t” = screenview; 

とゆーぶぶんが。
なんかで判定はできるんやろな。
うじゃうじゃ。

追記。2018/12/24
[iOS][Android][Tips] デバイスモデル名を取得する | DevelopersIO
上記サイトを見ると、C 言語のライブラリ関数である utsname を使用 するとわかるそうですが、
私には何のことかよくわかりませんでした。/(^o^)\

【Swift4】【autoLayout】レイアウトが決まってからの変更処理のタイミング

「autoLayoutってなんじゃー!つかいもんにならんわー!」
と、そんなふうに思っていた時期が僕にもありました。(笑)

使い慣れてみるとケッコー便利です。\(^o^)/

さて、そんな便利なautoLayoutですが、一つ困ったことが。
画面のサイズが決定して、それをもとに%などでサイズが決まって形状を変えたいときなどどの段階で処理するかが問題なのです。

viewControllerで描画に関連しそうなメソッドのライフサイクルはこんな感じかな?

さて、autoLayoutでのレイアウトが決定しているのがviewDidLayoutSubviews以降です。
それ以前は値が決定しておらず、autoLayoutで決まる値を参照にするとおかしなことになる可能性が高いです。

viewDidLayoutSubviewsを知る前はviewDidAppearにその処理を書いていました。
しかし、ビューが描画されたあとなので、一瞬描き変えられるところが見えたりします。/(^o^)\

また、毎回他のビューから戻ってきてビューが表示されるたびに同じ処理を繰り返してしまいます。
レイアウトが決まってビューが表示される直前のviewDidLayoutSubviewsなら描き変えられるところは表示されませんが、
こちらはビューの切り替わりだけでなくビューが回転したりイベントあるたびに呼ばれてしまうようです。

で、本題。
結局やりたいのはviewDidLayoutSubviewsで一回だけ処理をさせるということ。
いろいろと調べてこちらのサイトが参考になりました。

[Swift] viewDidLayoutSubviewsで初回だけ処理をしたい | 黒ごまのおむすび

Lazyというのをつかうらじいです。/(^o^)\

この処理だとビューの表示前に一回だけ呼び出すことができました。
他の人はこんな場合どうしているんだろう?
うじゃうじゃ。/(^o^)\

【Swift4】【CallKit】CXCall、電話の発着信等を取得する。

電話の着信時、受話時、終話時をアプリで受け取りたかったので調べてみた。
参考サイト

Xcode|iOS10から新しく加わったCallkitを使って通話の状態を取得してみた

上記サイトが役に立ちました。
ただ、Objective-cだったのでSwiftに変更。

CallKitのCXCallObserverのデリゲートでCXCallの値で送られてくるようだ。

CXCall – CallKit | Apple Developer Documentation
デベロッパサイトを見てgoogle翻訳

var uuid:UUID
コールの一意の識別子。
(よくわからん、とりあえず無視の方向で。w)

var isOutgoing:Bool
呼び出しが発信かどうかを示すブール値。
(コールが発信中か受信中か?)

var hasConnected:Bool
呼び出しが接続されているかどうかを示すブール値。
(通話中かどうか?)

var hasEnded:Bool
呼び出しが終了したかどうかを示すブール値。
(通話が終了したかどうか。)

var isOnHold:Bool
通話が保留状態かどうかを示すブール値。
(保留中?)

状態の変化があったときこれらの組み合わせで通知が来るようだ。
ちょっとややこしい。

とりあえずアプリで受けたいのは「着信」「受話」「終話」
なので下記のような感じで試してみた。

これで、家電からiPhoneに電話。「着信」「受話」「保留」「保留解除」「終話」と、「着信」から受けずに電話を切って「終話」。とやってみた。

まず着信
  通知があったよ!
  isOutgoing false
  hasConnected false
  hasEnded false
  isOnHold false

すべてfalseの通知が来ます。

続いてそこから電話を受けます。
  通知があったよ!
  isOutgoing false
  hasConnected true
  hasEnded false
  isOnHold false

保留にします。
  通知があったよ!
  isOutgoing false
  hasConnected true
  hasEnded false
  isOnHold true

保留を終わります。
  通知があったよ!
  isOutgoing false
  hasConnected true
  hasEnded false
  isOnHold false

電話を切ります。
  通知があったよ!
  isOutgoing false
  hasConnected true
  hasEnded true
  isOnHold false

もう一度着信は同じなので割愛して、電話を受けずに切った場合。
  通知があったよ!
  isOutgoing false
  hasConnected false
  hasEnded true
  isOnHold false

こうして見ると、hasConnectedだけでは「電話に出たとき」か「通話の終了」の通知かはわからず、hasEndedを含める必要があるのがわかります。
あ、しかも、これじゃ、着信から電話に出たのか、保留から電話にもどったかわからないじゃん。/(^o^)\
そこを区別するにはこちら側でなにか記録して区別するしか無いですね。

ところで、iPhoneの保留ってどうするか知ってますか?
試そうと思って家電からiPhoneに受話してみたが、保留の機能が見つからない!/(^o^)\
仕方ないので一旦切って調べてみると、「消音」の長押しだそうだ。
試そうと思って家電からiPhoneに受話してみたが、長押ししてみても保留にならない!/(^o^)\
仕方ないので一旦切って調べてみると、「消音」の3秒位長押しだそうだ。
そしてもういっかい試してみたらちゃんとできました。/(^o^)\
古い情報だと保留中は電子音とか書いてありますが、先程試した時は日本語で「保留中ですお待ち下さい」から音楽になっていました。
あと、通話中のスクリーンショットを撮ろうと思ったのですが、
通話する、「音量UP+サイドキー」を押すで、電話が切れる!/(^o^)\
タイミング悪かったかなと再度通話する、「音量UP+サイドキー」で、電話が切れる!/(^o^)\
どうも通話中はスクリーンショットが撮れないようです。
ちなみに画面収録も着話したら録画が切れました。通話中は記録できないようです。
なんかすっごい電話代無駄にした気がする。/(^o^)\

閑話休題/(^o^)\それはさておき、

形にしてみよう。
受話状態になったらこれを参考に
【Swift4】NotificationCenterの備忘録。 | iPhoneアプリ備忘録
通知が来るようにしてみた。

メイン画面にはLabelを。

電話を通知する方のクラス。

こんな感じで実現できました。\(^o^)/
GitHubが使えればコードをアップできるらしいけどよくわかんないんだよね。/(^o^)\

【Swift4】【UIDatePicker】のバグふたたび。UIDatePickerMode .countDownTimer

Swiftには、いや、Xcodeにはバグが有る。
2年前にもObjective-Cで泣かされた。
その時はなんとかなったけどそのバグはSwiftになっても直ってなかった。
とゆーか、よりひどくなってた。

そのバグはUIDatePickerのcountDownTimerモードにある。
マイナーすぎて使い所無い機能なのか、それ自体の情報も少ない。

UIDatePickerのcountDownTimerモードというのは、その名の通り、
24時間までのカウントダウンの時間を設定するためのピッカーだ。
ピッカーで10分と設定すると600秒の値が渡される。

さて、そのバグというのはピッカーで数値を決めても一回目のときはスルーされてしまうとゆーものだ。
二回目以降はちゃんとデータが渡されるのだが、一回目はスルーされてしまう。
10分を選んでも全然反映されない。
そのため、例えば10分に設定したい時は、一度11分にしてから10分にするなどというとてもめんどくさいことになる。
そんな状態ではとてもリリースできたものではない。

ネットを調べてみるといくつか情報はある。
古いのはこちら。
ios – UIDatePicker bug? UIControlEventValueChanged after hitting minimum internal – Stack Overflow
どうもiOS7の頃からあるバグらしい。
一応解決策も提示してあり、僕も2年くらい前にお世話になった。
割りと最近の記事ではこちら。
Xcode – DatePickerの値選択時の表示エラーが直らない|teratail
こちらはswift3での解決策が示してある。ま、Objective-cからSwiftに変わっただけでやってることは同じ。

そちらを参考にしてピッカーのモードをカウントダウンタイマーにしたあと、
バグ回避のためDispatchQueue処理を入れてみた。
(下記参照)

自分だけなのか、Swift4以降の仕様なのかこの状態ではやはり一回目の設定はスルーされた。
半日ほど試行錯誤したがどうしてもうまくいかない。
そんな中、キーボードを仕舞ってまたキーボードを出したときもまた同じく一回目がスルーされていることに気づく。

そうであれば、キーボードが出たときにバグの回避処置を入れればなんとかなるのでは無いか。
とゆーことで、ノーティフィケーションをつかい、キーボードが出たときに回避処置が入るようにしてみた。

こんな感じ。

結果は見事成功!\(^o^)/
なんとかカウントダウンのピッカーが使えるようになった。

時期に忘れてしまうと思うので備忘録として残しておく。/(^o^)\
あと、誰かの参考になったら嬉しい。

【Swift4】【Objective-c】SKStoreReviewController でアプリ内レビュー

最近流行りのアプリの中でレビュー(アプリの評価)をつけてもらうやつを調べてみた。

参考サイト
SKStoreReviewController を単純な条件で出すだけでも好意的なレビューが増えて良かった
SKStoreReviewController で App Store の評価数が急増した話 – wootan’s diary

機能としてはiOS10.3以降が対応のようです。
表示されるのは年に3回まで。
有意の時点で呼び出して、年に3回に達していないと表示されるらしい。
つまり、何回起動したときとか、そういう条件をつけて、レビューの請求をされてもおかしくない、
できれば好レビューが貰えそうな時に表示されるようにするのが良いっぽい。

コードとしてはStoreキットをインポートして使う。

Swiftの場合。

objective-cの場合。

一度この表示が出て評価を送信すると、その後は出なくなるのか、それとも、直後でも呼び出されるとまた表示されてしまうのか、そのへんの詳しい挙動がわからんのでそれなりの対応が必要そうです。

と、ゆーことで、いま手入れしてるobjective-c用にコードを書いてみた。

最初は10回呼ばれたら、その後は100回呼ばれる毎にレビューが請求されます。

あ、バージョンでの切り分けはこちらを参考にしました。
Objective-C で OS バージョン判定を簡潔に行う方法

【Swift】ちょっと珍しいエラー?【libclosured.dylib】

先日、アプリをアップデートして課金機能を実装しました。
ユーザーから課金しても良いので広告を外してほしいと熱望されていたのです。
しかし、ただでさえプログラミングに慣れていないのに、課金はかなり理解し難く、たいへん手を焼きました。
おかげで途中数ヶ月放置なんてこともあり、実装できるまでに1年近くかかりました。

で、Xcodeのシミュレータでも、実機でもsandBoxを使って問題なく課金が出来ることを確認して、Appleの審査に提出しました。
アプリの説明にも堂々と「課金で広告が外せるようになりました」と書いて。
最近は審査の時間がどんどん短くなってるようで、今回はびっくりの24時間切り、18時間位で無事リリースされました。

が、そこで問題が起こりました。
設定の画面から課金の画面に行くと、フリーズしてクラッシュするのです。
何度試しても同じ、実験機でも、嫁のiPhoneでも同様でした。
もう一度、Xcodeからビルドして動作確認をしてもこちらは問題なし。
appSotreからダウンロードし直してみるとやっぱりクラッシュ。
こんなんどうやって、デバグしたらいいのと、血の引く思いでした。
毎度、Appleに審査出して通ってから実地確認ってわけにも行かないしね。^^;
あんなに堂々と課金できますよって書いちゃったのに。/(^o^)\

で、まあ、とりあえずいろいろ検索して
下記サイト見つけエラーログが調べられることがわかりました。
swift – 原因のわからないバグの特定方法について – スタック・オーバーフロー

エラーログを取ってみるとズラズラとログが表示されて、

という感じで、最後は
0x1ad277000 – 0x1ad2a7fff libclosured.dylib arm64 /usr/lib/closure/libclosured.dylib
で終わっていました。

よくわからんけど、/usr/lib/closure/libclosured.dylib このあたりが怪しいかと検索しまくり。

日本語のページでは全く引っからなかったけど、海外のサイトではcrashという文字とともにいくつか引っかかりました。
CFNetwork crash with iOS 11.3.1 |Apple Developer Forums
My app crash with Exception Type:  EXC_CRASH (S… |Apple Developer Forums
Crash in iOS 11.1 | GeoNet

などなど…。

英語はよくわからんが、総じて見ると、原因不明なんだけどOSの問題ちゃうん?って感じでした。

で、AppleDeveloperに電話で聞いても見たのですが、症状の再現はされるけれど、原因はわからんと。
こっちでは無理なので、テクニカルサポートかフォーラムで聞いてみてくれと。
どっちも英語やん。\(^o^)/オワタ

仕方なしに、コードを見直してみたり、App Store Connectを見直してみたり、
、、、してたら、App Store Connect のApp内課金のページですっごい違和感が!

あれ?課金コンテンツって審査用のスクリーンショットで必須だよね?/(^o^)\
でも、設定されてないよね。/(^o^)\

【Swift】ちょっと珍しいエラー?【libclosured.dylib】

メタデータが不足って書いてあると審査用のスクリーンショットがなかったりします。

よくよくアプリの設定を見たら、Xcodeでもin-App purchaceもオンになってないし、全然課金が動作する状態じゃなかった。/(^o^)\
Xcodeのin-App purchaceがオンになっててスクリーンショットがなかったりしたらリジェクトもされたんだろうけど、すべて後回しにして数ヶ月放置している間にそういうこともすっかり忘れていたようです。
そりゃ課金の設定無いのに、プロダクト情報取りに行ったらフリーズもするわ。^^;
しかし、よくアプリの審査通ったな。/(^o^)\

で、スクリーンショットを撮って、App内課金の設定もして、速攻で再度審査に提出しました。

まあはよ気づいてよかったよ。
こんなこともあるんだね。
うじゃうじゃ。

【Swift4】Google Analytics と eコマース?

SwiftでGoogle Analytics で eコマースを利用しようとしたが、ワケワカラン。
そもそもeコマースがわかってないんだが。
とりあえず課金があったらその情報を受け取れるようにしてみたい。

とりあえず、まずここの説明がSwiftに対応していない。(# ゚Д゚)
e コマース トラッキング – iOS SDK  |  iOS 向けアナリティクス  |  Google Developers

コピペだが、このコードがSwift化できれば良いんだと思う。

参考にしたのがこちら
ios – Issue with Google Analytics in Swift 2 or 3 – Stack Overflow
と、こちら
iOSアプリでもGoogleAnalyticsでがっつり計測する実装方法【総集編】 | Nagisaのすゝめ

なんだかんだイジって、
Xcodeに文句言われないように変更して出来たのがこの形。

とりあえず自分用。
うまくいかなくったってしーらないしーらない。
うじゃうじゃ。/(^o^)\

【Swift4】tabbarとnavigationControllerを使ってる時のマイナーな?戻り方。

Tabbar Controllerでつながった、タブAの画面とタブB1の画面があり、タブBからはnavigationControllerをつかってタブB2の画面に遷移できるとする。
画面B2に居る時にtabbarでタブAに移動して、またタブBに移動しようとすると、タブB2に移動してしまう。

一旦、タブAに移動してタブBに移動するとき、自動的にタブB1に移動するようにしたかった。

言葉にするとすごくわかりにくい。orz

結論から書くとタブB2の画面が消えたあとにnavigationControllerで戻る指示をすれば良い。

こんな感じで。
うじゃうじゃ。