- 投稿日:2021-01-25T23:34:15+09:00
エラー「Application tried to present modally a view controller」の時
環境
・Xcode: 12.3
・Swift5以下のようなエラーログがでてクラッシュしてしまった時
libc++abi.dylib: terminating with uncaught exception of type NSException *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Application tried to present modally a view controller <ProjectName.ViewController: 0x158331780> .that has a parent view controller <UINavigationController: 0x159877a00>.' terminating with uncaught exception of type NSExceptionこんなパターンがあるよ
上記のようにViewControllerに対してNavigationControllerがいる場合に、
ViewControllerを表示しようとしてしまっている可能性let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil) if let navigationVc = storyboard.instantiateInitialViewController() as? UINavigationController, let viewController = navigationVc.topViewController as? ViewController { // これは× // present(viewController, animated: true, completion: nil) // これは○ present(navigationVc, animated: true, completion: nil) }これに限らないかと思いますが、誰かの役に立てば。
- 投稿日:2021-01-25T18:43:13+09:00
inputViewを使ってカスタムキーボードをつくろう!(Swift)
はじめに
UITextField
やUITextView
で入力する際はキーボードの入力タイプが色々用意されていますが実はこのキーボード色々カスタムができます!たぶん実用的なのは
UIPickerView
とかUIDatePicker
を表示したりとかです(なんか昔半角カナ用キーボードを作った記憶があるけどあんまよくなかった)。これからはもう
UIKit
を使うことも少なくなっていくのかもしれませんが備忘録として。。。*注:「つくろう」って書きましたがとくにカスタムキーボードを薦めるものではありません。
UITextFieldをカスタマイズ
試しに
UITextField
のキーボードをカスタムしてみます。やり方は簡単でinputView
に任意のView
を設定するだけで OK です// こんなのとか let keyboard = CustomKeyboardView(frame: .init(origin: .zero, size: .init(width: 284, height: 284))) keyboard.delegate = self textField.inputView = keyboard // こんなのとか let picker = UIPickerView() picker.delegate = self picker.dataSource = self textField.inputView = pickerあとはデリゲートとかで入力文字を受け取って
UITextField
に表示するだけです。こんな感じ。
カスタム View ピッカー カスタム View はこんな感じです。
xib
protocol CustomKeyboardViewDelegate: AnyObject { func customKeyboardView(_ customKeyboardView: CustomKeyboardView, didSelectKey key: String) } final class CustomKeyboardView: UIView { weak var delegate: CustomKeyboardViewDelegate? override init(frame: CGRect) { super.init(frame: frame) loadNib() } required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder)! loadNib() } private func loadNib() { let view = Bundle.main.loadNibNamed("CustomKeyboardView", owner: self, options: nil)?.first as! UIView view.frame = bounds view.translatesAutoresizingMaskIntoConstraints = true view.autoresizingMask = [.flexibleWidth, .flexibleHeight] addSubview(view) } @IBAction private func selectKey(_ sender: UIButton) { delegate?.customKeyboardView(self, didSelectKey: sender.titleLabel!.text!) } }キーボードの高さはわりと自由みたいです
サクッとカスタムキーボードが表示できました!ただカスタムキーボードを入力制限目的で利用するにはいくつか問題があるのでオススメしませんコピペとか外部キーボード接続とか。。。
コピペに関しては
UITextField
のカスタムクラスをつくってcanPerformAction
ごにょごにょすればいける気がしますが外部キーボードに関しては防げないと思われます(そもそもあんまりUITextField
で入力制限なんてしない方がいいと思います)。UIControlをカスタマイズ
とくに
UITextField
のinputView
に設定する!でも問題ないのですがUITextField
を使うとなるとレイアウトの自由度が低いのでせっかくなんでカスタムクラスをつくった方が自由度が高いのでおすすめです下記のように
UIControl
を継承したクラスをつくってcanBecomeFirstResponder
とinputView
を設定してやるとカスタムキーボードが表示できます。UIView
じゃないのはaddAction
がしたかったからです。フォーカス時がわかりにくいので枠線の色を変更しています。protocol PickerInputControlDelegate: AnyObject { func pickerInputControl(_ pickerInputControl: PickerInputControl, didSelectValue value: String) } final class PickerInputControl: UIControl { var items = [String]() weak var delegate: PickerInputControlDelegate? override init(frame: CGRect) { super.init(frame: frame) commonInit() } required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder)! commonInit() } private func commonInit() { layer.cornerRadius = 8.0 layer.borderWidth = 1.0 layer.borderColor = UIColor.systemGray.cgColor addAction(.init(handler: { [weak self] _ in self?.isSelected.toggle() if self?.isSelected == true { self?.becomeFirstResponder() } else { self?.resignFirstResponder() } }), for: .touchUpInside) } override var inputView: UIView? { let picker = UIPickerView() picker.delegate = self picker.dataSource = self return picker } override var canBecomeFirstResponder: Bool { return true } override var isSelected: Bool { didSet { layer.borderColor = isSelected ? UIColor.systemBlue.cgColor : UIColor.systemGray.cgColor } } @discardableResult override func becomeFirstResponder() -> Bool { let value = super.becomeFirstResponder() isSelected = isFirstResponder return value } @discardableResult override func resignFirstResponder() -> Bool { let value = super.resignFirstResponder() isSelected = isFirstResponder return value } } extension PickerInputControl: UIPickerViewDelegate { func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { delegate?.pickerInputControl(self, didSelectValue: items[row]) } } extension PickerInputControl: UIPickerViewDataSource { func numberOfComponents(in pickerView: UIPickerView) -> Int { return 1 } func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { return items.count } func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { return items[row] } }こんな感じで
UIControl
なので中にView
置き放題です。
UIPickerViewDelegate
,UIPickerViewDataSource
,items
は外に出した方が汎用性が高いかもstatic cellと組み合わせる
おまけで
UITableViewController
の static cell と組み合わせるとフォーカス時に自動スクロールしてくれるので複数の入力項目がある場合はおすすめですこんな感じ。
おわりに
カスタムキーボードを作る際はタップ領域をちゃんと確保してあげると素敵です。ちょっと古い記事ですが下記の記事はキーボードのタップ領域とか詳しく調べていておもしろいです。
わりとお世話になった手法だけどこれからはあんまり使わなくなるのかな
UIKit
。。。調べてて気になったけどこいつ知らねえ
参考
- 投稿日:2021-01-25T12:53:05+09:00
iPhoneで快適な音楽ライフを 〜ショートカット×Ever Play×FLAC〜
サマリー
iPhoneのショートカットアプリは様々な一連の操作の簡略化に一役買っている。中でもミュージックアプリとの相性は良く、連携させる事でAirPods接続時や帰宅時をトリガーとしたBGMやプレイリストなどの自動再生ができるなど便利な点が多い。しかし、この連携は純正のミュージックアプリ間に限られており、またミュージックアプリおよびiTunesはFLACファイルの管理・再生を行うことができないことが著者の長年の不満となっている。
今回私はショートカットに対応した非純正アプリ"Ever Play"を発見した。このアプリは純正のミュージックアプリやiTunesで対応していないFLACファイルの再生に対応しているため、iPhoneでより高音質な音楽を楽しむことができる。また、ショートカットアプリとの連携が可能になったことで、より便利にミュージックアプリを扱える環境を整備することができた。イントロ 〜ショートカット×ミュージック〜
iPhoneの様々な煩雑な作業をまとめて効率化してくれる便利アプリ、ショートカット。このアプリはミュージックアプリと連携することが可能である。例えば、iPhoneがAirPods(orその他のBTイヤホン)に接続することをトリガーとしてお気に入りのプレイリストを自動再生させたり、帰宅して宅内Wi-Fiに接続したことをトリガーとしてAirPlayを介したBGMの自動再生させたり、することができる。
ショートカットAppで操作できるミュージックアプリは純正アプリにしか対応していない
こんなにも便利なショートカットだが欠点がある。それは数あるミュージックアプリの中でもApple純正のミュージックアプリにしかショートカットが対応していないことである。そのため、純正以外に愛用している音楽アプリやSpotify、Amazon music、LINE musicなどの音楽配信アプリをショートカットでコントロールすることができない。
(Amazon musicのプレイリストの自動再生は対応させることに成功している。
→ショートカットを利用したAmazon Musicのプレイリスト自動再生)。"ショートカットとの連携が優先だったら大人しく純正アプリ使えばいいじゃん"と思うかもしれないが、もうひとつの難点がそれを阻んでいる。
純正アプリおよびiTunesはFLACに対応していない
音楽好きにとってiPhoneが好まれない仕様が様々あるが、中でも私が特に感じている事はApple純正アプリとiTunesで FLACと呼ばれるフォーマットの音楽ファイルのライブラリーへの追加・再生ができないことである。
FLAC(フラック、Free Lossless Audio Codec)は、オープンフォーマットの可逆圧縮音声ファイルフォーマットである。〜〜〜〜〜。可逆圧縮であるため、元の音声データからの音質の劣化が無い。
(Wikipediaより転載、FLACについてのページ)CDをリッピングする際、CDと変わらないクオリティであるWAVフォーマットはファイルサイズが大きいことが難点であるが、FLACは無劣化の可逆圧縮が可能であるため、WAVとほぼ同等音質を保てる上に(異論は認める)、ファイルサイズが小さく、さらにジャケット写真などのタグをファイル内に保存できるのでとても優秀なフォーマットである。音楽配信サイトのレコチョクでは、FLACフォーマットでハイレゾ音源の配信を行なっており、私も全ての音源やレンタルしたCDはすべてこのFLACフォーマットで保存・管理している。
こんな便利なファイルにも関わらず、iTunesは頑なにこのフォーマットに対応しようとしてこなかった。iPhone側はiOS11以降FLACフォーマットの再生に対応しているのに、だ。「iPhone X」「iPhone 8」はFLAC再生をサポートし、「iOS 11」で「iPhone 7」でも再生が可能
私は約4年ほどiTunesのFLAC対応を待っているが一向に対応する気配を見せない。これは意図的だと私は考える。
iTunesのFLAC形式対応のアプデ早くしてくれないかなぁ。
— ⌬ベんぜん (@MONO_Benzene) June 26, 2017
そのため、Appleが変わるのを待つのではなく、こちらがなんとか変わる他なくなってしまった。最も無難な解決策として、全てのFLACファイルをiTunesに対応しているALACフォーマット等に変換することが挙げられる。これは膨大な音楽データを変換することが大変手間である上に、管理しているストレージ内でのデータ量がほぼ倍に増えてしまう点で絶対にやりたくない。iTunesはFLACを扱えないわけではない
いろいろ調べてみると、iTunesではミュージックライブラリにFLACを読み込むことができないだけで、ファイル共有機能を使用すればPC内部のFLACフォーマットの楽曲をiPhoneへ転送することが可能であることがわかった。さらに、いくつかのミュージックアプリではFLACファイルの再生が可能であることもわかった。やや希望の光が差し込んできた瞬間である。そこで、FLACファイルの再生に対応した音楽アプリを探す旅を始めた。
FLACファイル対応アプリ: Ever Play
長らくFLACファイル対応のミュージックアプリを探す旅をしたが、結論としてiPhoneに最適なアプリはEver Playというものに自分の中で落ち着いた。
Twitterでサーチした感じからして日本人のユーザーはほとんどいないものと思われる。他の有名どころとしてfoobar2000もあるが、ひらがなやカタカナ名のアーティストが重複して表示されてしまうバグがあったため却下した。
Ever Playが素晴らしいのはFLACに対応しているのはもちろんのこと、WAV・DSDといったハイレゾ音源に使用される音楽フォーマットの再生までカバーしていることである。それでいて動作はとても軽く、歌詞表示までできてしまう優れものである。気に食わないことはアルバムアーティストのカラムがないこと・プレイリストがインポートできないこと(これは事実か追って検証する必要がある)があるが、これは仕方なく目を瞑る(今後のアップデートによる追加を祈る)。※プレイリストのインポート-likeなことは一応できなくもない。iTunesのファイル共有で楽曲の転送を行う際に、プレイリスト単位で送信すると、転送後のフォルダがプレイリストに対応するため一応プレイリスト単位での曲の管理や再生ができる。
Ever Play、アップデートによるショートカット対応
アップデートに全く気づけなかったのだが、Ever Playがいつのまにかショートカットに対応していた。しかもこれがただ全曲を再生するだけではなく、ショートカットアプリでの制御と同様に、再生するリストの範囲を変えることができる点がとても良い。
試しにショートカットを作ってみる。
ホーム画面の右上のショートカットアプリのマークをタップすると、
このまま"Add Shortcut to Siri"を押すと、ショートカットアプリに全曲再生のショートカットが登録されている。"Shuffle"をオンにすれば全曲シャッフルのショートカットを登録できる。
ここで注意点が1つ。シャッフルのオンオフはこの画面で作成した時のみに変更可能で、ショートカット登録後には変更できない。
ホーム画面の右上のショートカットアプリのマークをタップ後の画面で、青色で下線のある"All Songs"をタップすると、このように再生範囲を"Playlist"、"Album"、"Artist"、"Genre"から選び、どの範囲で再生を行うか選ぶことができる。
例えば、"Playlist"を選べばどのプレイリストを再生するかの選択が表示される。まぁ便利。一つ残念なのは"File"が選択できないこと。
試しにお気に入りの曲のプレイリストを作成し、シャッフル再生のショートカットを組んでみるとこんな感じになる。
ミュージック×ショートカットの使用例
これでようやくミュージックアプリをショートカットの支配下におけたので、いろいろとショートカットを作ってみることにした。
1. 帰宅時に自宅のBTスピーカーから自動でBGMが再生されるようにする
これが一番やりたかったこと。帰宅したら自然とジャズが流れている自室という環境が作ってみたかった。
以下のようにショートカットを組んでみた。
メディアの出力先をBTスピーカーに変更し、音量をちょうど良い大きさの45%に変更し、Ever Playでジャズのプレイリストを再生する、といった感じ。これによって
玄関前でショートカット起動 -> 玄関の鍵解錠 -> リビングルームのシーリングライト点灯 -> デスクの照明点灯 -> ジャズが流れる
という一連の工程を全て自動化させることに成功した。2. 複数の自動再生ショートカットから選択して自動再生する
ここで作成した複数の音楽再生ショートカットの中からどれか一つを選んで再生するショートカットを作成したいと思う。
構想としてはこんな感じ。
・プレイリストやアーティストの自動再生ショートカットをひたすら作る
・ショートカットアプリ内で1つのフォルダに集約する
・集約したフォルダ内から1つを選んで再生するようなショートカットを組むまず、お気に入りのプレイリストなりアーティストなりを再生するためのショートカットを先ほど述べた手順で作成する。
次に、作成したショートカットを1つのグループにまとめる。Ever Playアプリのショートカット用のフォルダを作成し、そこへ先ほど作成したショートカットを登録する。
そして最後に、そのフォルダを参照して1つのショートカットを選択して起動させるショートカットを組む。
余談
iTunesとミュージックアプリはいい加減FLACフォーマットの再生に対応しろ、以上。
- 投稿日:2021-01-25T11:53:16+09:00
Xcode12以降でシミュレータビルドできない時の対処法
Xcode12以降でシミュレータビルドできない時の対処法
- こちらはメモ的なものになります
- いろいろ調べて対応したので残しておきます。
対応方法
project -> architectures -> Excluded Architectures
ここに
- arm64を記入
- Any SDKのままでもビルドできたけど、AnyiOS SDKの方が良いと思う
Pods関係
- これを記入する
post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64" end end end参考
https://developer.apple.com/forums/thread/656509
https://stackoverflow.com/questions/63391793/xcode-12-build-target-in-wrong-order-for-simulator/63405201#63405201
- 投稿日:2021-01-25T09:08:59+09:00
【ionic】面倒臭がりでもできる、アラーム機能
時計やスケジュール張のアプリはもちろんですが、漫画アプリやコミュニケーションツールなどなど。通知機能はいろんなスマホアプリで活用されています。
今回はその中のアラーム通知をスマホアプリで実装する方法を「ionic」というフレームワークを用いて紹介させていただきたいと思います。
アラーム設定方法
基本的にはアラームの方法は2種類あります。
プッシュ通知(リモート通知)
オンラインでの通知をすることができます。例えば、アプリ提供側から新着情報をユーザに提供したい場合はこれを使って通知を送ることができます。ローカル通知
プッシュ通知とは逆にオフラインの機能になります。ユーザが自分で時間を設定して、その時間に通知を受け取りたい場合に使えます。一般的にアラームとなるとローカル通知をイメージされる方が多いと思います。今回はそのローカルでの通知を使ったアラーム機能を紹介します。
ionic公式より
ionicの公式ページにLocal Notificationという機能があります。これを使えば簡単に実装できます。
import { LocalNotifications } from '@ionic-native/local-notifications/ngx'; constructor(private localNotifications: LocalNotifications) { } ... // Schedule delayed notification this.localNotifications.schedule({ text: 'Delayed ILocalNotification', //現在時刻の10秒後にアラーム。1秒=1000。 trigger: {at: new Date(new Date().getTime() + 10000)}, led: 'FF0000', sound: null });工夫を加えて実装
やりたいこと:複数のアラームを同時にかけたい。
タスクリストなどを作成し、それぞれに時間を設定。その時間になったらそれぞれのアラームが起動し、通知がくるようなアプリを想定してコードを書いていきます。
//以下のように配列にユーザが入力した時間とタイトル(通知が来たときのメッセージ)をもつような状態にする todoList = [ { time: [15,30], schedule:'おやつ' }, { time: [18,50], schedule:'夜ご飯' }, ]todoAlerm() { const dateHour = new Date().getHours(); const dateMinutes = new Date().getMinutes(); //現在時間を「分」に置き換える const dateTime = dateHour * 60 + dateMinutes; for (let i = 0; i < this.todoList.length; i++) { let timeHour = this.todoList[i].time[0]; let timeMinute = this.todoList[i].time[1]; //設定した時間を「分」に置き換える const setAlerm = parseInt(timeHour, 10) * 60 + parseInt(timeMinute, 10); //設定した時間から現在時刻の差をとり、「秒」にする const disTime = (setAlerm - dateTime) * 60; ///ionic notification のtriggerに合う形に直していく const datedistime = new Date().getTime() + disTime * 1000; const alermsettime = new Date(datedistime); //音声の設定(AndroidとiOSで指定する音が異なるためどちらの端末か特定している) //ちなみにionicのplatform機能を利用 if (this.platform.is('android')) { this.isAndroid = true; } //表示するメッセージを設定 const alermtext = this.todoList[i].schedule + 'の時間になりました!'; //現在時刻と設定時刻との差(disTime)が0以上だったら発動、そうでなかったらアラートを出す if (disTime >= 0) { this.localNotifications.schedule([ { //ここがミソ!!アラームをいくつか設定する時はidを連番にする必要がある。 id: i + 1, text: alermtext, trigger: {at: alermsettime}, led: 'FF0000', //Androidなら'file://sound.mp3'、そうでない(iOS)なら'file://beep.caf' sound: this.isAndroid ? 'file://sound.mp3': 'file://beep.caf' } ]); this.alertSetting = true; } else { this.alertSetting = false; alert(this.todoList[i].imageName.kanji + 'の時間を現在時刻より後に設定してください!。'); } } } }以上です!!
ポイントは主に2つです。
triggerに設定する時間
時間の形式はどんな形でも良いわけではなく、フォーマットがあります。最初は少しここでつまづきました。1秒を1000とする決まりもあるので初心者の方は注意してください。いくつか同時に仕掛けたいならidを連番にする
for文等を利用してidを連番にしないと上手く通知が表示されないので注意しましょう。以上に注意すれば簡単にアラームを機能をつけることができます。
ionicフレームワークは他にも機能をたくさん揃えています。ほとんどが公式サイトにコードごと載っています。それを自分なりにカスタムしてより良いアプリやサービスを作っていけるといいですね!
機会があれば是非試してみてください!ちなみに、以下のQRコードは実際に初学者である開発者が上記のアラーム機能を使って作ったスマホアプリです。「やることリスト」という機能に使われています。
- 投稿日:2021-01-25T00:08:12+09:00
【swift】文字列を一文字ずつに分割する時のArray("hoge")の型について
文字列を1文字ずつに分割したい!
競技プログラミングを解いているとよく出くわしますね。解決方法としては
let str = "hoge" let separatedStr = Array(str) print(separatedStr) // ["h", "o", "g", "e"]という感じで
Array()
の()の中に文字列を入れれば、一文字ずつ分割してくれます。でも、これで分割された文字列の配列は
String
型ではなくCharacter
型なのです。
Character
型とはdeveloper.apple.comによると、
"A single extended grapheme cluster that approximates a user-perceived character."
(google翻訳: ユーザーが知覚する文字を近似する単一の拡張書記素クラスター。)なんか難しく書いてますがとりあえず、
「人間が自然に認識できる最小の"1文字の型"」
と認識していただければ大丈夫です。問題なのは、
String
型とは明確に違うってことです。
特にInt型に変換するときに問題になってきます。Int型への変換(例: ABC083B - Some Sums)
2桁以上の数字に対して、各桁の和を算出したいです。
(例: "13"だったら1+3 = 4、"624"だったら6+2+4 = 12)まず、与えられた数字列を1文字ずつに分解します。
let digits = "13" //String型 let separatedDigits = Array(digits) //Character型の配列 print(separatedDigits) //["1","3"]さて、当然Int型に直さないと計算できないので、
この後["1","3"]
を[1,3]
に直します。ただ
String
型の感覚でlet intDigits = separatedDigits.map({Int($0)!})ってやったら
Cannot convert value of type 'String.Element' (aka 'Character') to expected argument type 'String'
というエラーが出ます。
swift5のVersion 12.3ではCharacter
型から直接Int
型には変換できないためです。なので、
let intDigits = separatedDigits.map({Int(String($0))!})として一旦
String
型を経由させてください。その後はいつも通り
reduce
で配列内の足し算を行えばいいので、let result = intDigits.reduce(0, +) print(result) //4で導出完了です。
コード全文
let digits = "13" let separatedDigits = Array(digits) let intDigits = separatedDigits.map({Int(String($0))!}) //数字列の分割 let result = intDigits.reduce(0, +) print(result) //4参考文献