- 投稿日:2020-11-13T22:53:55+09:00
Swift初学者が電子レンジロジックを実装してみた!
概要
現在、私はMENTAサービスを利用し、ヤマタクメンターにご指導いただいています。
ヤマタクメンターやサービスについて知りたい方はこちら!
(https://menta.work/plan/584)その中で、今回は、応用学習の第2段ということで、
「電子レンジロジックの実装」の課題を行ったので、アウトプットしていきます。なお、今回の課題はSwift初学者が作ったロジックであり、至らぬ点が多々あると思います。どうか、温かい目で見守っていただけると幸いです。
アドバイス等ありましたら、ご教授いただけると幸いです。
環境
-Playground
実装の要件
・電子レンジのワット数は900,600,200とする
・今回は、安全のために11分以上は加熱できないものとする・タイマーの実装
・分と秒の概念を実装
・ 残り時間の出力
・残り時間が0秒になったら、「温め終了です」と出力し、タイマーを停止
・温める時間が11分以上だと「温めを開始できません!」、
「タイマーを10分59秒以内に設定してください」と出力・タイマーが10分59秒以内にセットされたら、温め可能フラグを返し、「温め開始!!」を出力
・選択されたワット数を出力実装
VirtualVendingMachine.rbimport UIKit import PlaygroundSupport class VirtualMicrowave : UIViewController { //タイマーをセットしてください var timeSet = (min:0,sec:0) //Timerクラスの初期化 var timer: Timer! //ワット数を決める enum powerConsumptionType{ case bottun900W case bottun600W case bottun200W var displayName: String{ switch self { case.bottun900W : return "900W" case.bottun600W : return "600W" case.bottun200W : return "200W" } } } override func viewDidLoad() { super.viewDidLoad() //スタートタイマーを呼び出す startTimer() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } //温め可能フラグを返す func startWarm(type: powerConsumptionType) -> Bool { var isWarmable = false if timeSet.min < 11 { print("温め開始!!:\(type.displayName)") return true } return isWarmable //カウントダウンタイマーを呼び出す countDownTimer() } @objc func countDownTimer(){ if timeSet.min == 0 && timeSet.sec == 0 { print("温め終了です") timer.invalidate() }else if timeSet.min > 10{ print("温めを開始できません!") print("タイマーを10分59秒以内に設定してください") timer.invalidate() }else if timeSet.sec > 0 && timeSet.min < 11 { //秒数が0以上の時または10分以下の時 timeSet.sec -= 1 print("\(timeSet.min)分\(timeSet.sec)秒") } else if (timeSet.sec == 0) && timeSet.min > 0 { //秒数が0の時 timeSet.sec += 59 timeSet.min -= 1 print("\(timeSet.min)分\(timeSet.sec)秒") } } //タイマーを起動するメソッド func startTimer() { timer = Timer.scheduledTimer( timeInterval: 1, target: self, selector: #selector(self.countDownTimer), userInfo: nil, repeats: true) } } let window = UIWindow(frame: CGRect(x: 0, y: 0, width: 300, height: 500)) let viewController = VirtualMicrowave() viewController.view.backgroundColor = UIColor.gray window.rootViewController = viewController window.makeKeyAndVisible() PlaygroundPage.current.liveView = window let virtualMicrowave = VirtualMicrowave() var isSuccessToWarm = virtualMicrowave.startWarm(type:.bottun200W) print(isSuccessToWarm)まとめ
今回は、引数の使い方やPlaygroundの環境ではあるが、タイマーメソッドの使い方を学んだ。
ゼロからロジックを考えたので、基礎構文の理解が深まった。
追加機能を考えるとすれば、レンジ機能だけではなくオーブン、トースト機能などを追加してもいいかもしれません。
- 投稿日:2020-11-13T20:01:48+09:00
CIFilterのblurぼかしで画像を小さく縮ませない方法
画像が縮む問題
CIFilterのCIGaussianBlurでCIImageをぼかすと、画像がShrinkしてしまう。
let blurredImage = CIFilter(name: "CIGaussianBlur", parameters: [kCIInputImageKey:originalCIImage, kCIInputRadiusKey:50])?.outputImage?CGImageを操作したり、AccelerateやMetalPerformanceShader、OpenCVのフィルターを使うと元と同じサイズのぼかし画像が得られるけれど、メモリ使用が大きい。
CoreImageのなかで完結すると、何枚ぼかしても数MBしかメモリを使わない。方法
let cropSize = CGRect(x: 0, y: 0, width: originalCIImage.extent.width * 0.999, height: originalCIImage.extent.height * 0.999) let blured = CIFilter(name: "CIGaussianBlur", parameters: [kCIInputImageKey:originalCIImage, kCIInputRadiusKey:50])?.outputImage?.cropped(to: cropSize).resize(as: originalCIImage.extent.size)Cropすると画像サイズに合わせてくれます。
リサイズメソッドはこちら↓
extension CIImage { func resize(as size: CGSize) -> CIImage { let selfSize = extent.size let transform = CGAffineTransform(scaleX: size.width / selfSize.width, y: size.height / selfSize.height) return transformed(by: transform) } }?
お仕事のご相談こちらまで
rockyshikoku@gmail.comCore MLを使ったアプリを作っています。
機械学習関連の情報を発信しています。
- 投稿日:2020-11-13T19:44:53+09:00
iOS13のPopover修正について
はじめに
今更ですが、iOS13からpopoverのmaskの重なり順が変更になっていたようで。。。
気付いたらpopoverで表示しているpickerなどが見切れていたので、
修正方法を備忘録としてまとめます。環境
swift5
Xcode 11.4.1修正前
UIのズレはこんな感じです。
左右に矢印がある時にはっきり見切れてしまいます。
ちなみに元々の実装はこんな感じにしてました。
hoge.swift// ImagePicker表示 let imagePicker = UIImagePickerController() imagePicker.delegate = self imagePicker.sourceType = .photoLibrary imagePicker.modalPresentationStyle = .popover imagePicker.mediaTypes = [kUTTypeMovie as String] let popover = imagePicker.popoverPresentationController popover?.sourceView = button popover?.sourceRect = button.bounds present(imagePicker, animated: true, completion: nil)修正方法
UIViewControllerのextensionを作成しました。
UIViewController+Picker.swiftextension UIViewController { /// Picker用のVCを作成し、Pickerを表示する(imagePicker用) /// /// - Parameters: /// - imagePickerController: UIImagePickerController /// - source: pickerの表示元となるView func presentPickerView(_ imagePickerController: UIImagePickerController, source: UIView) { guard let imagePickerView = imagePickerController.view else { return } // 土台となるVCを作成します let customPickerViewController = UIViewController() // まず先にaddChildします customPickerViewController.addChild(imagePickerController) // 土台のVCとimagePickerを同じ大きさにします imagePickerController.view.frame = customPickerViewController.view.frame // subViewになりうるviewにAutoLayoutをコード上で指定する場合はfalseにする imagePickerController.view.translatesAutoresizingMaskIntoConstraints = false // addSubviewします customPickerViewController.view.addSubview(imagePickerView) // ここで土台VCのsafaAreaに対してImagePickerの制約を指定をしていきます NSLayoutConstraint.activate([ imagePickerController.view.leadingAnchor.constraint(equalTo: customPickerViewController.view.safeAreaLayoutGuide.leadingAnchor), imagePickerController.view.trailingAnchor.constraint(equalTo: customPickerViewController.view.safeAreaLayoutGuide.trailingAnchor), imagePickerController.view.topAnchor.constraint(equalTo: customPickerViewController.view.safeAreaLayoutGuide.topAnchor), imagePickerController.view.bottomAnchor.constraint(equalTo: customPickerViewController.view.safeAreaLayoutGuide.bottomAnchor) ]) // didMoveでset完了 imagePickerController.didMove(toParent: customPickerViewController) // 以下はpickerの基本的な実装に戻ります。 // 土台のViewに大きさを指定(=pickerの大きさ) customPickerViewController.preferredContentSize = CGSize(width: 200, height: 200) customPickerViewController.modalPresentationStyle = .popover let popover = customPickerViewController.popoverPresentationController popover?.sourceView = source popover?.sourceRect = source.bounds present(customPickerViewController, animated: true, completion: nil) } }使う時はこんな感じ
ViewController.swift@IBOutlet func TapButton(_ sender: UIButton) { // 表示したいpickerを作成 let imagePicker = UIImagePickerController() // delegateとかは表示するclassで設定する imagePicker.delegate = self imagePicker.sourceType = .photoLibrary // extensionを使用して表示 presentPickerView(imagePicker, source: button) }おわり。
- 投稿日:2020-11-13T19:38:52+09:00
[Android][iOS]Crashlytics導入メモ
1. Androidメモ
build.gradle(app)への設定の注意
Execution failed for task ‘:app:uploadCrashlyticsMappingFileRelease’. > Expected file collection to contain exactly one file, however, it contains no files.とかいうエラーが出た時の原因と対処
前提
build.gradleで「minify true」を設定している場合、ビルド時に難読化が適用され、mapping.txtが作成される。
「minify false」の場合、ビルド時に難読化されず、mapping.txtは作成されない。原因と対処
「minify false」かつ「mappingFileUploadEnabled true」を設定すると、ビルド時にmapping.txtが作成されていなくても、FirebaseSDKはmapping.txtをFirebaseにアップロードしようとして、上記のエラーが発生する。したがって、「minify false」を設定する場合は、「mappingFileUploadEnabled false」(またはmappingFileUploadEnabled自体を書かないデフォルトfalseだから)を設定する必要があり、
「minify true」を設定する場合は、「mappingFileUploadEnabled trueまたはfalse」どちらでもよい(ただし、mappingFileUploadEnabled falseの場合、Firebaseのクラッシュログは難読化されているので、クラッシュログ見てもよくわかんなくなりそう)。参考
- https://firebase.google.com/docs/crashlytics/upgrade-sdk?platform=android#firebase-crashlytics-sdk_7
- https://developer.android.com/studio/build/shrink-code#enable
Crashテスト方法
- 以下のコードを任意のActivityのonCreate()に仕込む
throw new RuntimeException("Test Crash"); // Force a crash
- ビルドしてアプリを起動して上記Activityの画面まで遷移し、クラッシュさせる。
- アプリをキルして再起動して、Firebaseにクラッシュレポートとしてあがってくるのをまつ(最長5分かかるらしい)
2. iOSメモ
Crashテスト方法
- 以下のコードをトップの画面のソースに付け加える。
override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. let button = UIButton(type: .roundedRect) button.frame = CGRect(x: 20, y: 50, width: 100, height: 30) button.setTitle("Crash", for: []) button.addTarget(self, action: #selector(self.crashButtonTapped(_:)), for: .touchUpInside) view.addSubview(button) } @IBAction func crashButtonTapped(_ sender: AnyObject) { fatalError() }
- ビルドしてアプリを起動してCrashボタンをタップしてクラッシュさせる
- アプリをキルして再起動して、Firebaseにクラッシュレポートとしてあがってくるのをまつ(最長5分かかるらしい)
※Xcodeと端末を接続してデバッグ状態だとうまく動かないので注意。
- 投稿日:2020-11-13T16:50:31+09:00
[Swift]キーボード上にあるツールバーの高さを動的に変更する方法
はじめに
UITextFieldやUITextViewで表示されるキーボード上のツールバーに、バー上のボタンをタップするともう1段バーが表示される方法になります。
イメージとしてはiOS純正のリマインダーアプリで実装されている、リマインド時間やロケールを選択するツールバーを同じものになります。
textView.inputAccessoryView = toolbar
でキーボード上にツールバーを設定する基本的な方法はよく見かけますが、
動的にバーの高さを変更する方法が見当たらなかったので参考になれば幸いです。完成イメージ
ツールバー上のベルのボタンをタップすると、もう1段上に通知時間のインターバルを選択するボタンが表示されます。
実装方法
はじめは
textView.inputAccessoryView = toolbar
で設定したtoolbarの高さを動的に変更しようとしたのですが、なかなかうまくできず。。。
(実装方法わかる方いれば教えてください!)
今回はtextView.inputAccessoryView = toolbar
で設定したtoolbarに対して子viewをaddSubview
、sendSubviewToBack
で裏側に隠しておいて、
上下に移動することで表示を切り替えています。今回はUITableViewにCustomTableViewCellを作成し、セル上にUITextViewを配置しています。
キーボード上にツールバーを表示するためにCustomTableViewCell内でUITextViewに対してキーボードが表示されるようにします。CustomTableViewCell.swiftlet lowerToolbar = LowerToolbar(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 50)) lowerToolbar.initToolbarButton(item: item!) lowerToolbar.delegate = self textView.inputAccessoryView = lowerToolbar上記で設定しているlowerToolbarの中身がこちらになります
LowerToolbar.swiftprotocol LowerToolbarDelegete: class { func onTouchToolbarButton(selectedTimeInterval: Int) } class LowerToolbar: UIView { @IBOutlet weak var toolbar: UIToolbar! var upperToolbar: UIView? var isHidenUpperToolbar: Bool = true var upperToolbarCenterY: CGFloat = 0 weak var delegate: LowerToolbarDelegete! = nil let oneHourButton = UIButton(type: .system) let threeHourButton = UIButton(type: .system) let fiveHourButton = UIButton(type: .system) var item: ItemModel? var selectedTimeInterval: Int = 0 // Last tapped button. override init(frame: CGRect){ super.init(frame: frame) loadNib() } required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder)! loadNib() } func loadNib(){ let view = Bundle.main.loadNibNamed("LowerToolbar", owner: self, options: nil)?.first as! UIView view.frame = self.bounds self.addSubview(view) toolbar.clipsToBounds = true upperToolbar = UIView(frame: CGRect(x: 0, y: -1, width: self.frame.size.width, height: 61)) upperToolbar?.backgroundColor = UIColor.toolbar self.addSubview(upperToolbar!) self.sendSubviewToBack(upperToolbar!) upperToolbarCenterY = upperToolbar!.center.y } func createToolbarButton(btn: UIButton, title: String, timeInterval: Int) { btn.setTitle(title, for: .normal) btn.tag = timeInterval btn.layer.borderWidth = 1 btn.layer.borderColor = UIColor.toolbarBorder.cgColor btn.layer.cornerRadius = 20 btn.contentEdgeInsets = UIEdgeInsets(top: 10, left: 20, bottom: 10, right: 20) btn.backgroundColor = UIColor.toolbarButton btn.addTarget(self, action: #selector(tapHoursButton), for: .touchUpInside) upperToolbar?.addSubview(btn) if self.item?.timeInterval == timeInterval { btn.layer.borderColor = UIColor.systemBlue.cgColor btn.backgroundColor = UIColor.systemBlue btn.tintColor = UIColor.white } } func initToolbarButton(item: ItemModel) { self.item = item createToolbarButton(btn: oneHourButton, title: "1hour", timeInterval: 60) oneHourButton.translatesAutoresizingMaskIntoConstraints = false oneHourButton.leadingAnchor.constraint(equalTo: upperToolbar!.leadingAnchor, constant: 20).isActive = true oneHourButton.topAnchor.constraint(equalTo: upperToolbar!.topAnchor, constant: 10).isActive = true createToolbarButton(btn: threeHourButton, title: "3hour", timeInterval: 180) threeHourButton.translatesAutoresizingMaskIntoConstraints = false threeHourButton.leadingAnchor.constraint(equalTo: oneHourButton.trailingAnchor, constant: 20).isActive = true threeHourButton.topAnchor.constraint(equalTo: upperToolbar!.topAnchor, constant: 10).isActive = true createToolbarButton(btn: fiveHourButton, title: "5hour", timeInterval: 400) fiveHourButton.translatesAutoresizingMaskIntoConstraints = false fiveHourButton.leadingAnchor.constraint(equalTo: threeHourButton.trailingAnchor, constant: 20).isActive = true fiveHourButton.topAnchor.constraint(equalTo: upperToolbar!.topAnchor, constant: 10).isActive = true } func initUpperToolbar() { upperToolbar?.center.y = upperToolbarCenterY isHidenUpperToolbar = true } func decorateTappedHourButton(btn: UIButton) { btn.layer.borderColor = UIColor.systemBlue.cgColor btn.backgroundColor = UIColor.systemBlue btn.tintColor = UIColor.white } func decorateNormalHourButton(btn: UIButton) { btn.layer.borderColor = UIColor.toolbarBorder.cgColor btn.backgroundColor = UIColor.toolbarButton btn.tintColor = .systemBlue } func decorateHourButton(btn1: UIButton, btn2: UIButton, btn3: UIButton, newTimeInterval: Int) { if selectedTimeInterval == newTimeInterval { selectedTimeInterval = 0 decorateNormalHourButton(btn: btn1) } else { selectedTimeInterval = newTimeInterval decorateTappedHourButton(btn: btn1) } decorateNormalHourButton(btn: btn2) decorateNormalHourButton(btn: btn3) } @objc func tapHoursButton(btn: UIButton) { switch btn.tag { case 60: decorateHourButton(btn1: oneHourButton, btn2: threeHourButton, btn3: fiveHourButton, newTimeInterval: btn.tag) case 180: decorateHourButton(btn1: threeHourButton, btn2: oneHourButton, btn3: fiveHourButton, newTimeInterval: btn.tag) case 400: decorateHourButton(btn1: fiveHourButton, btn2: oneHourButton, btn3: threeHourButton, newTimeInterval: btn.tag) default: print("no item") } delegate?.onTouchToolbarButton(selectedTimeInterval: btn.tag) } override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { if isHidenUpperToolbar { let rect = self.bounds return rect.contains(point) } else { var rect = self.bounds if rect.contains(point) { return rect.contains(point) } rect.origin.y -= 60 return rect.contains(point) } } @IBAction func tapBellButton(_ sender: Any) { if isHidenUpperToolbar { UIView.animate(withDuration: 0.1, animations: { self.upperToolbar!.center.y -= 60 self.isHidenUpperToolbar = false self.layoutIfNeeded() }) } else { UIView.animate(withDuration: 0.1, animations: { self.upperToolbar!.center.y += 60 self.isHidenUpperToolbar = true self.layoutIfNeeded() }) } } }解説
LowerToolbar.swiftfunc loadNib(){ let view = Bundle.main.loadNibNamed("LowerToolbar", owner: self, options: nil)?.first as! UIView view.frame = self.bounds self.addSubview(view) toolbar.clipsToBounds = true upperToolbar = UIView(frame: CGRect(x: 0, y: -1, width: self.frame.size.width, height: 61)) upperToolbar?.backgroundColor = UIColor.toolbar self.addSubview(upperToolbar!) self.sendSubviewToBack(upperToolbar!) upperToolbarCenterY = upperToolbar!.center.y }LowerToolbarを初期化する際に、UpperToolbarを作成し、
addSubview
してsendSubviewToBack
で背面に移動することで非表示にします。LowerToolbar.swift@IBAction func tapBellButton(_ sender: Any) { if isHidenUpperToolbar { UIView.animate(withDuration: 0.1, animations: { self.upperToolbar!.center.y -= 60 self.isHidenUpperToolbar = false self.layoutIfNeeded() }) } else { UIView.animate(withDuration: 0.1, animations: { self.upperToolbar!.center.y += 60 self.isHidenUpperToolbar = true self.layoutIfNeeded() }) } }ベルボタンをタップした際に、upperToolbarを上下に60移動しています。
LowerToolbar.swiftoverride func point(inside point: CGPoint, with event: UIEvent?) -> Bool { if isHidenUpperToolbar { // upperToolbarが非表示で // lowerToolbarがタップされた場合 let rect = self.bounds return rect.contains(point) } else { // upperToolbarが表示されているが // upperToolbarがタップされた場合 var rect = self.bounds if rect.contains(point) { return rect.contains(point) } // lowerToolbarがタップされた場合 rect.origin.y -= 60 return rect.contains(point) } }ポイントなのがこの部分で、upperToolbarを60上に移動した場合、親viewのlowerToolbarのframe領域からはみ出してしまうため、
upperToolbarの上に乗っているボタンのイベントを受け取れなくなってしまいます。
そこで、override func point(inside point: CGPoint, with event: UIEvent?) -> Bool
をオーバーライドして
upperToolbarがタップされた場合にイベントが受け取れるようにしています。その他気づき
今回はlowerToolbarの中でupperToolbarを生成してコードで作成しました。
これは、upperToolbarをstoryboardで個別に作成したものを使用したところ、
delegateを使用してタップされたイベントをupperToolbar->lowerToolbar->CustomTableViewCellへ移譲することができなかったためです。
(lowerToolbarでupperToolbarのdelegate設定できなかったです。)まとめ
ツールバーの高さを動的に変更できれば一番シンプルな実装にはなるかと思いますが、
高さの変更が思ったようにできず、違う方法でトライしてみました。
アップルの純正リマインダーアプリではどうやって実装してるんですかね。気になります。
何か参考になれば嬉しいです。
- 投稿日:2020-11-13T16:21:37+09:00
【Flutter】Firebase環境構築(iOS/Android)
参考文献
- Flutter アプリに Firebase を追加する
- cloud_firestore 0.13.6
- firebase_core 0.5.2
- 64K を超えるメソッドを使用するアプリ向けに multidex を有効化する
- 【Flutter実践】Firebase環境構築と、Firestoreのデータを取得してアプリで表示
iOS編
1. Firebaseプロジェクト作成
2. iOSアプリの追加
- iOSバンドルID
3. GoogleService-Info.plist
- Xcodeプロジェクトに追加
4.FlutterFire プラグインを追加する
- firebase_core導入
- cloud_firestore導入
Android編
1.Androidアプリの追加
- Androidパッケージ名
AndroidManifest.xml<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="sample">2.google-services.jsonの追加
android/app ├── build.gradle ├── google-services.json //追加 └── src3.Firebase SDK の追加
android/build.gradledependencies { classpath 'com.google.gms:google-services:4.3.4' //追加 }android/app/build.gradleapply plugin: 'com.google.gms.google-services' //追加android/app/build.gradleandroid { defaultConfig { ... minSdkVersion 15 targetSdkVersion 28 multiDexEnabled true } ... } dependencies { implementation 'com.android.support:multidex:1.0.3' }
- 投稿日:2020-11-13T15:02:33+09:00
Xcodeでアーカイブしたファイルを再度表示する
以前にアーカイブしたiOSアプリをもう一度使いたいときってありませんか?
私は、これが分からなくてもう一度ビルドし直した経験があります...が、時間の無駄ですよね、、笑Xcodeのアーカイブ保管庫
iOSアプリを開発して公開する際、製品版としてビルドすると、これまでにアーカイブしたファイルが
一覧になってるウィンドウが表示され、最新のものをApp Storeへアップロードする形になるかと思います。でも、そのウィンドウの名前が分からない...
Archivesウインドウとかって名前にしておいてくれたら良いのに。。?
これを、改めて表示する方法です!Organizerの表示方法
Xcodeでアーカイブ保管庫(Organizer)は、Windowのメニューの中にあります。
まとめ役・整理箱という意味
Organizer(オーガナイザー)とは
辞書などで調べると、組織者や主催者という訳がよく出てきますが、、以下のような意味もあるようです。
オーガナイザーはもともと「組織の勧誘活動を行う者」という意味の言葉で、社会運動の分野で使われていた言葉ですが、現在では幅広い分野で使われており、「オルグ」と略して使うことも多いです。
例えば「オーガナイザーを務める」で「まとめ役を務める」というような意味になります。
引用)https://imikaisetu.goldencelebration168.com/archives/3736
Xcodeでは、「整理箱」という意味で使われてるのだと思います。
- 投稿日:2020-11-13T14:25:33+09:00
SwiftUIでCoreDataを動的に検索・表示する
はじめに
SwiftUI で CoreData を利用してデータを管理するアプリを作成する中で、UIKit における UISearchBar のような機能が欲しくなりました。
ユーザーの入力に応じて動的に表示データを変えるというのは、まさに SwiftUI の得意技だと思いますが、SwiftUI には同等の機能を提供する View が用意されておらず、また、CoreData との連携方法にひと工夫必要です。本稿ではこのような UI を作る上で必要となる、ユーザーの入力に応じて動的に CoreData のDBからデータを検索し、結果を表示する方法について説明します。
なお、本稿では、SwiftUI ならびに CoreData それぞれの基本的な説明は割愛します。
開発環境等
- MacBook (Retina, 12inch, Early 2015)
- macOS Catalina Version 10.15.7
- Xcode Version 12.1 (12A7403)
- iOS 14.1 (iPhone X 実機で動作確認済み)
課題
入門記事などでは、SwiftUIでCoreDataを利用するための基本的な実装として以下のようなコードがよく紹介されています。
ItemListView1.swiftstruct ItemListView1: View { @Environment(\.managedObjectContext) var context @FetchRequest(entity: Item.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Item.title, ascending: true)], predicate: NSPredicate(format: "title CONTAINS[C] \'hoge\'"), animation: .default) var items: FetchedResults<Item> var body: some View { List { ForEach(items) { item in HStack { Text(item.title!) } } } } }注) CoreDataを利用するために、AppDelegate.swift などで、
NSPersistentCloudKitContainer
オブジェクトを生成するなどの準備も必要ですが、本稿では CoreData 利用の基本説明は割愛しています。これは、文字列型の title プロパティに文字列"hoge"を含む item オブジェクトをDBから取得しリスト表示する例ですが、検索する文字列は固定となっています。
やりたいのは、TextField でユーザーが入力した文字列をもとに、動的にデータを検索・取得することです。どうしたら良いでしょうか?
試行錯誤
まず考えられるのは以下のようなコードです。
TextField でユーザーが入力したデータをserchWord
に代入し、それを@FetchRequest
のpredicate
に設定するという考え方です。※このコードではエラーとなります。
ItemListView2.swiftstruct ItemListView2: View { @Environment(\.managedObjectContext) var context @FetchRequest(entity: Item.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Item.title, ascending: true)], predicate: NSPredicate(format: "title CONTAINS[C] %@", searchWord), animation: .default) var items: FetchedResults<Item> @State private var searchWord: String var body: some View { VStack { TextField("search word", text: $searchWord) List { ForEach(items) { item in HStack { Text(item.title!) } } } } } }実際にコードを書いてみると、
@FetchRequest()
のpredicate
を設定する部分でエラーとなります。
プロパティの初期化に、初期化されていない他のプロパティを使うことができないのです。では、どうすれば良いのでしょうか?
解決策
パターンA
まず、検索文字列を入力する View と、それをもとに List 表示する View を sub view として分けます。
後者の sub view のinit()
で、検索文字列を受け取り、FetchRequest
を設定します。
init()
の中で、FetchRequest
を設定するのは、self.items
ではなく、self._items
であるところがミソです。
sel.items
は get only なので、何かを set するにはその本体であるself._items
に対して行う必要があるのです。ItemListView3.swiftstruct ItemListView3: View { @Environment(\.managedObjectContext) var context @State private var searchWord: String = "" var body: some View { VStack { TextField("search word", text: $searchWord) ItemListView3sub(searchWord: searchWord) } } } struct ItemListView3sub: View { @Environment(\.managedObjectContext) var context @FetchRequest var items: FetchedResults<Item> var searchWord: String init(searchWord: String) { self.searchWord = searchWord self._items = FetchRequest(entity: Item.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Item.title, ascending: true)], predicate: NSPredicate(format: "title CONTAINS[C] %@", searchWord), animation: .default) } var body: some View { List { ForEach(items) { item in HStack { Text(item.title!) } } } } }パターンB
まず、検索文字列を入力する View と、それをもとに List 表示する View を分ける所は一緒ですが、子View に対して、
FetchRequest
を丸ごと渡します。ItemListView4.swiftstruct ItemListView4: View { @Environment(\.managedObjectContext) var context @State private var searchWord: String = "" var body: some View { VStack { TextField("search word", text: $searchWord) ItemListView4sub(items: FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Item.title, ascending: true)], predicate: NSPredicate(format: "title CONTAINS[C] %@", searchWord))) } } } struct ItemListView4sub: View { @Environment(\.managedObjectContext) var context @FetchRequest var items: FetchedResults<Item> var body: some View { List { ForEach(items) { item in HStack { Text(item.title!) } } } } }パターンAに比べパターンBの方がシンプルに思えます。
ただし、パターンAの方は、例えば、受け取った文字列が空文字列だった場合は検索条件を変える、などといったキメの細かい処理を行うことが可能になります。最後に
SwiftUI や CoreData はまだまだ情報が少なく、ちょっと突っ込んだ使い方をしたい場合、意外と情報が見つからないことが多いように思います。
この記事が少しでもお役に立てれば幸いです。
参考
- 投稿日:2020-11-13T12:15:23+09:00
キーチェーンとそのコマンドたち
きっかけ
最近キーチェーンを調べなければならないシーンがあったのですが、circleCI上での話だったのでCUIしか使えず。。。
GUIでしかキーチェーンの操作したことがなかった僕はとても困ったので記事にしました。キーチェーン概要
赤い枠の部分がキーチェーンたちで、「ログイン」が1個のキーチェーンになってます。
そして青い枠で、選択されているキーチェーン内にある項目の何を表示するかの絞り込みを行ってます。システムが認識できるキーチェインは2つで、
システムのキーチェインが1つとユーザーのキーチェインが1つのみになります。たぶん。。キーチェイン作成
security create-keychain {{キーチェーンパス}}
キーチェイン一覧
security list-keychains
システム側とユーザー側とで設定されているキーチェイン2つを表示します。
-d u
オプションでユーザーのキーチェーンが確認できます。読み込むキーチェインを追加します。
security list-keychains -s {{キーチェーンパス}}
キーチェインに証明書を追加します。
security import {{証明書パス}} -k {{キーチェーンパス}} -P {{password}} -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild
-T
オプションで、パスワードなしで許可するアプリを設定します。複数指定可能です。キーチェインの設定を行います。
security set-keychain-settings -t 3600 -l {{キーチェーンパス}}
-l
オプションは、スリープした時にキーチェーンをロックします。
-t 3600
オプションは、3600秒後にキーチェーンをロックします。実際にやってみよう!
キーチェーン一覧
まずはキーチェーン一覧を見てみます。
/Users/maruiyugo/Library/Keychains/login.keychain-db
が画像の「ログイン」というキーチェーンです。
/Library/Keychains/System.keychain
が「システム」キーチェーンですね。$ security list-keychains "/Users/maruiyugo/Library/Keychains/login.keychain-db" "/Library/Keychains/System.keychain"キーチェーン作成と設定
marui.keychain-db
キーチェーンを作成し、キーチェーンの読み込みを行いました。
最後にキーチェーンのリストを表示させています。
するとlogin.keychain-db
がmarui.keychain-db
に入れ替わったではありませんか!
キーチェーンアクセスをみても「ログイン」が「marui」になっています。$ security create-keychain ~/Library/Keychains/marui.keychain-db password for new keychain: retype password for new keychain: $ security list-keychains -s ~/Library/Keychains/marui.keychain-db $ security list-keychains "/Users/maruiyugo/Library/Keychains/marui.keychain-db" "/Library/Keychains/System.keychain"キーチェインに証明書を追加
適当なp12ファイルを追加してみました。
$ security import {{p12}} -k ~/Library/Keychains/marui.keychain-db -P {{password}} -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuildキーの中身を見てみると、オプションで指定した「codesign」「security」「productbuild」が許可されています。
まとめ
キーチェーンは今まで流されるままに使っていたので、今回調べて詳しくなれてよかったです。
記事がよかった!とかためになった!とか思ったら、励みになるのでLGTMおしてください!ないてよろこびます(๑>◡<๑)
- 投稿日:2020-11-13T01:42:21+09:00
子供用の学習アプリケーションを作る(1)
はじめに
昨日、筋萎縮性側索硬化症(ALS)と呼ばれる病気についての記事を見かけ、以前より知っている病気ではあったもののなぜ、この病気になってしまうのかについては、知らなかった。
ネットで調べてみたところ、様々なサイトで解説されているものの、言葉が難しかったり、文字数が多く読むのが大変であった。そこで、そのような知識を分かりやすく、まとめ尚且つ大人から子供まで使えるような学習アプリケーションを自作してみました。
実装
まず、環境構築については、下記の記事を見ていただければと思います。
参考: React Nativeをはじめる(環境構築)ディレクトリ構成
使用したUI component, ライブラリ, フリーイラスト
UI component
1.React-native-elements
React-Native-Elementsは、フリーのクロスプラットフォームのUIcomponentです。
色々な開発者が作成したcomponentが集められ、クオリティもかなり高いです。2.React-native-paper
React-native-paperは、使いやすく設計されているUIcomponentです。
componentは、Googleのマテリアル基準に準拠しているのでデザイン性がかなり高いという感想です。ライブラリ
1.React Navigation
React Navigationは、画面遷移のライブラリです。
なぜ、このライブラリが必要かというと、Reactでは画面遷移のための機能が用意されていないからです。React Navigationでは3つの方法で画面遷移ができます。
1.stack:スライドしながら画面遷移する方法
2.tabs:タブを選択することで画面遷移する方法
3.drawer:画面端から現れるメニューで画面遷移する方法今回は、1の方法で実装を行いました。
フリーイラスト
1.manypixels
manypixelsは、フラットなイラストをフリーでdlできます。App.js
このファイルではメインとなるページの処理を記載しています。
App.jsimport 'react-native-gesture-handler'; import * as React from 'react'; import {Image, SafeAreaView, ScrollView, StyleSheet} from 'react-native'; import {Text, Card, Button} from 'react-native-elements'; import {NavigationContainer} from '@react-navigation/native'; import {createStackNavigator} from '@react-navigation/stack'; import AppHeader from './components/header'; import contentsSelect from './components/contentsSelect'; const HomeScreen = ({navigation}) => { return ( <SafeAreaView style={styles.container}> <ScrollView> <AppHeader /> <Text h2 style={styles.hearderMessage}> For explorer who want to know the unknown </Text> <Text style={styles.hearderMessageBody}> ilearnはさまざまなトピックスをまとめ{'\n'}発信する場所だよ。 </Text> <Card> <Card.Title>探検をはじめよう!</Card.Title> <Card.Divider /> <Image source={require('../public/img/teach.png')} style={styles.cardImage} /> <Text style={styles.cardText}> 医学,科学,数学,コンピュータ・サイエンス,宇宙,{'\n'}歴史をまなぼう。 </Text> <Button style={styles.cardButtonText} title="出発する?" onPress={() => navigation.navigate('Contents')} /> </Card> </ScrollView> </SafeAreaView> ); }; const Stack = createStackNavigator(); const App = () => { return ( <NavigationContainer> <Stack.Navigator> <Stack.Screen name="Home" component={HomeScreen} /> <Stack.Screen name="Contents" component={contentsSelect} /> </Stack.Navigator> </NavigationContainer> ); }; const styles = StyleSheet.create({ container: { flex: 1, }, headerImg: { width: 90, height: 30, top: 60, }, hearderMessage: { marginTop: 100, textAlign: 'center', }, hearderMessageBody: { flex: 1, top: 20, marginBottom: 60, textAlign: 'center', fontWeight: 'bold', color: '#93a5b1', }, cardImage: { width: 350, height: 350, }, cardText: { marginBottom: 10, }, cardButtonText: { marginTop: 20, marginBottom: 20, }, }); export default App;header component
名前から察するようにヘッダーについてまとめたファイルになります。
header.jsimport React from 'react'; import {View, StyleSheet, Image} from 'react-native'; const AppHeader = () => { return ( <View> <Image source={require('../../public/img/Header.png')} style={styles.headerImg} /> </View> ); }; const styles = StyleSheet.create({ headerImg: { width: 90, height: 30, marginLeft: 20, top: 30, }, }); export default AppHeader;contents component
こちらのcontents componentは、ユーザが実際に何のコンテンツについて学びたいかを選ばせるページの処理をまとめています。
contentsSelect.jsimport React from 'react'; import {SafeAreaView, ScrollView, StyleSheet} from 'react-native'; import {Card, Title} from 'react-native-paper'; import AppHeader from './header'; const contentsSelect = () => { return ( <SafeAreaView style={styles.container}> <ScrollView> <AppHeader /> <Card style={styles.cardPadding}> <Card.Content> <Title style={styles.cardTitle}>宇宙って?</Title> <Card.Cover source={require('../../public/img/alien.png')} style={styles.cardImg} /> </Card.Content> </Card> <Card style={styles.cardPadding}> <Card.Content> <Title style={styles.cardTitle}>ALSって知ってる?</Title> <Card.Cover source={require('../../public/img/health.png')} style={styles.cardImg} /> </Card.Content> </Card> <Card style={styles.cardPadding}> <Card.Content> <Title style={styles.cardTitle}>Falcon 9がすごい</Title> <Card.Cover source={require('../../public/img/startup_isometric.png')} style={styles.cardImg} /> </Card.Content> </Card> </ScrollView> </SafeAreaView> ); }; const styles = StyleSheet.create({ container: { flex: 1, }, cardImg: { height: 300, }, cardPadding: { marginBottom: 20, }, cardTitle: { fontWeight: 'bold', }, }); export default contentsSelect;動作
次回
次回は、コンテンツ選択した後の詳細画面を作り込んでいこうと思います。
おわり
かなり気合を入れて作成を始めたので、随時記事も更新していこうと思います。
また、React-vativeをさわってみたい方に対し、開発コードがお役に立てば幸いです。参考
React-native
React-native-elements
React Navigation
React-native-paper
manypixels
難病情報センター: ALS
- 投稿日:2020-11-13T00:24:47+09:00
TextFieldと連携したDatePickerを二つ作る
はじめに
UIDatePickerを2つ作り、TextFieldにインプットする方法をまとめてみます。
開発環境
macOS Catalina 10.15.7
Xcode 12.1
Swift 5.3テキストフィールドの設置と変数の準備
まず、テキストフィールド2つをStoryBoard上で任意の場所に設置します。
今回は1つ目のテキストフィールドをATextField、2つ目をBTextFieldとしました。また、以下の2つの変数を用意します。
ViewController.swiftclass ViewController: UIViewController { @IBOutlet weak var ATextField: UITextField! @IBOutlet weak var BTextFiled: UITextField! var APicker: UIDatePicker! var BPicker: UIDatePicker! override func viewDidLoad() { super.viewDidLoad() } }DatePickerを作成するメソッドの定義
次に、TextFieldに入れるDatePickerを作成するメソッドを定義します。
ViewController.swiftfunc makePicker(_ isA:Bool) -> UIDatePicker { let myPicker:UIDatePicker! myPicker = UIDatePicker() myPicker.tag = isA ? 1 : 2 myPicker.datePickerMode = .date myPicker.locale = NSLocale(localeIdentifier: "ja_JP") as Locale myPicker.preferredDatePickerStyle = .wheels return myPicker }作成するDatePickerは2つあるので、引数にtrueかfalseをとる形にし、trueであればATextFieldに挿入するDatePickerが、falseであればBTextFiledに挿入するDatePickerが作成されます。
それぞれのDatePickerにタグを1、2とふっておきます。
DatePickerの設定についてはこのメソッド内で自由に設定します。iOS14でのDatePickerの挙動についてはこちらの記事を参考にしました。
→https://qiita.com/kj_trsm/items/a53b0b3f7e1bc7c06106DatePickerの変更を検知するメソッドの定義
DatePickerがユーザーによって操作された際に入力された日付をテキストフィールドに表示するメソッドを定義します。
ViewController.swift@objc internal func dateChanged(sender: UIDatePicker){ let formatter: DateFormatter = DateFormatter() formatter.dateFormat = "yyyy年M月d日" let selectedDate = formatter.string(from: sender.date) if sender.tag == 1 { ATextField.text = selectedDate } else { BTextFiled.text = selectedDate } }まずDate型をString型に変換するDateFormatterを用意し、変換した文字列をselectedDate変数に格納した後、senderのタグに従って入力先のテキストフィールドに文字列を代入します。
ここまでできたら、先ほど定義したmakePickerメソッド内で、myPickerにaddTargetします。
ViewController.swiftfunc makePicker(_ isA:Bool) -> UIDatePicker { let myPicker:UIDatePicker! myPicker = UIDatePicker() myPicker.tag = isA ? 1 : 2 myPicker.datePickerMode = .date myPicker.locale = NSLocale(localeIdentifier: "ja_JP") as Locale myPicker.preferredDatePickerStyle = .wheels //ここ myPicker.addTarget(self, action: #selector(onDidChangeDate(sender:)), for: .valueChanged) return myPicker }キーボードを引っ込ませる処理
このままだと、DatePickerの操作を始めるとキーボードが出っ放しになるので、
キーボード外をタップするとキーボードが引っ込む処理を行います。ViewController.swiftoverride func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { ATextField.endEditing(true) BTextFiled.endEditing(true) }ViewDidLoad内でDatePickerを作成
最後に、ViewDidLoad内でDatePickerを作成するメソッドを呼び、テキストフィールドにインプットして完成です。
ViewContoller.swiftoverride func viewDidLoad() { super.viewDidLoad() APicker = makePicker(true) ATextField.inputView = APicker BPicker = makePicker(false) BTextFiled.inputView = BPicker }コード全体
ViewCOntroller.swiftclass ViewController: UIViewController { @IBOutlet weak var ATextField: UITextField! @IBOutlet weak var BTextFiled: UITextField! var APicker: UIDatePicker! var BPicker: UIDatePicker! override func viewDidLoad() { super.viewDidLoad() APicker = makePicker(true) ATextField.inputView = APicker BPicker = makePicker(false) BTextFiled.inputView = BPicker } func makePicker(_ isA:Bool) -> UIDatePicker { let myPicker:UIDatePicker! myPicker = UIDatePicker() myPicker.tag = isA ? 1 : 2 myPicker.datePickerMode = .date myPicker.locale = NSLocale(localeIdentifier: "ja_JP") as Locale myPicker.preferredDatePickerStyle = .wheels myPicker.addTarget(self, action: #selector(onDidChangeDate(sender:)), for: .valueChanged) return myPicker } @objc internal func onDidChangeDate(sender: UIDatePicker){ let formatter: DateFormatter = DateFormatter() formatter.dateFormat = "yyyy年M月d日" let mySelectedDate = formatter.string(from: sender.date) if sender.tag == 1 { ATextField.text = mySelectedDate } else { BTextFiled.text = mySelectedDate } } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { ATextField.endEditing(true) BTextFiled.endEditing(true) } }