- 投稿日:2019-10-08T23:11:22+09:00
<iOS> 「iOS10以上」と「iOS9.3.5以下」を互換性を持たせてCoreDataを使う
目的
今回、初めてXcodeを触って、アプリを作ってみた。
ただ、XcodeとSwiftのバージョンが新しいのに、実機で持っていたのが「iOS9.3.5」だった。仮想デバイスですら「iOS10」なのにである。デバッグしてみると、悲しいことにいろんなところで調べたことが古くて使えないよ!と言われ、困り果て、何とかして両方使えるように共存できないか探ってみたら、何と似たようなことで困っていた人が記事にしてくれていたので、何とか助かった。
調べても、問題が解決しない記事が多かったので、今回自分でまとめることにした。備忘録。
何が問題?
「iOS10以降」と「それ以下」の違いが大きく関わっている。
・CoreDataの宣言の仕方が割と違う。
→ そもそも、AppDelegateで宣言されているものが違うので、使えるメソッドも違う。・AppDelegateで宣言されているものが違う
→ 前述したように、宣言されているものが違うので、Contextの宣言方法もまるで違う。※因みに、古いXcodeで作ればいいやんと思ってやってみたけど、macOS Mojaveでは、うまくXcodeの共存(つまり、Xcodeのバージョンが古いのと新しいのを両方とも置くこと)ができない。起動時にエラーが出る。(StackOverFlowでも探したけど、みんな同じことが起こって諦めている感じだった。)
対応方法
ここでやりたいのは「共存」。どっちでも使えるようにしたいと考え、以下のように対応した。
- 古いXcodeで宣言されているコードを、新しい方に移植する。
無ければ入れれば良い話で、そこからメソッドを呼び出せばいい。
古いバージョンのXcodeで宣言されているAppDelegateのメソッドを、新しいXcodeのAppDelegateに記述すれば、古い方からもそのバージョンにあったContextを呼び出せるという考え。
今回作ったアプリ
AppDelegate
AppDelegate.swiftimport UIKit import CoreData @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { /*ここまでは、新しいXCodeでは自動的に定義されている。 ------------------------------------------------------------------- ここから下が、古いXcodeからの移植 */ lazy var applicationDocumentsDirectory: NSURL = { let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) return urls[urls.count-1] as NSURL }() lazy var managedObjectModel: NSManagedObjectModel = { // The managed object model for the application. This property is not optional. It is a fatal error for the application not to be able to find and load its model. //forResource: には自分のAppの名前を入れる。 let modelURL = Bundle.main.url(forResource: "MyAppName", withExtension: "momd")! return NSManagedObjectModel(contentsOf: modelURL)! }() lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = { // The persistent store coordinator for the application. This implementation creates and returns a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail. // Create the coordinator and store let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel) //appendingPathComponentの引数には、データベース名.sqlite let url = self.applicationDocumentsDirectory.appendingPathComponent("MyDB.sqlite") var failureReason = "There was an error creating or loading the application's saved data." do { try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: nil) } catch { // Report any error we got. var dict = [String: AnyObject]() dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data" as AnyObject? dict[NSLocalizedFailureReasonErrorKey] = failureReason as AnyObject? dict[NSUnderlyingErrorKey] = error as NSError let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict) // Replace this with code to handle the error appropriately. // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. NSLog("Unresolved error \(wrappedError), \(wrappedError.userInfo)") abort() } return coordinator }() lazy var managedObjectContext: NSManagedObjectContext = { // Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail. let coordinator = self.persistentStoreCoordinator var managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType) managedObjectContext.persistentStoreCoordinator = coordinator return managedObjectContext }() /*ここまでをまずは移植する。 ------------------------------------------------------------------------ */ // MARK: - Core Data Saving support @available(iOS 10.0, *) func saveContext () { let context = persistentContainer.viewContext if context.hasChanges { do { try context.save() } catch { // Replace this implementation with code to handle the error appropriately. // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. let nserror = error as NSError fatalError("Unresolved error \(nserror), \(nserror.userInfo)") } } } //iOS9以下のCoreData_Saveメソッド これも移植したもの。 func saveContext9 () { if managedObjectContext.hasChanges { do { try managedObjectContext.save() } catch { // Replace this implementation with code to handle the error appropriately. // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. let nserror = error as NSError NSLog("Unresolved error \(nserror), \(nserror.userInfo)") abort() } } } }Contextの宣言
ViewController.swiftfunc getData(){ //ここでiOSのバージョンによって処理が変わる。 if #available(iOS 10.0, *) { //Contextの宣言(iOS10) let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext do{ sample = try context.fetch(Sample.fetchRequest()) }catch{ print("Error") } }else{ //Contextの宣言(iOS9) let context = (UIApplication.shared.delegate as! AppDelegate).managedObjectContext do{ sample = try context.fetch(Sample.fetchRequest()) }catch{ print("Error") } } }CoreData-Save編-
Add.swiftif #available(iOS 10.0, *) { let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext let defaultAction:UIAlertAction = UIAlertAction(title: "OK!", style: UIAlertAction.Style.default) { (action: UIAlertAction!) -> Void in let sample = Sample(context: context) sample.id = Int32(self.AutoIncrements()) sample.name = self.nameField.text sample.mail = self.mailField.text sample.pass = self.passField.text (UIApplication.shared.delegate as! AppDelegate).saveContext() self.navigationController!.popViewController(animated: true) } alert.addAction(defaultAction) } else { // Fallback on earlier versions let context9:NSManagedObjectContext = (UIApplication.shared.delegate as! AppDelegate).managedObjectContext let defaultAction:UIAlertAction = UIAlertAction(title: "OK!", style: UIAlertAction.Style.default) { (action: UIAlertAction!) -> Void in //ここからが新しいXcodeと違うところ let entity = NSEntityDescription.entity(forEntityName: "Sample", in: context9) let sample = NSManagedObject(entity: entity!, insertInto: context9) as! Sample sample.id = Int32(self.AutoIncrements()) sample.name = self.nameField.text sample.mail = self.mailField.text sample.pass = self.passField.text (UIApplication.shared.delegate as! AppDelegate).saveContext9() self.navigationController!.popViewController(animated: true) } alert.addAction(defaultAction) }終わりに
まだまだ記録できていないところはあるけれど、とりあえずここまで。
暇なときに追記していきます。参考サイト
-- xcode8でCoreData使うときにiOS9以前も対応に含めるときに --
https://qiita.com/fujiwarawataru/items/12903b6e8fe42d6464f2ここでヒントをもらいました。@fujiwarawataruさん。この記事を書いていただき感謝です。助かりました。
- 投稿日:2019-10-08T23:11:22+09:00
<iOS:Swift> 「iOS10以上」と「iOS9.3.5以下」を互換性を持たせてCoreDataを使う
目的
今回、初めてXcodeを触って、アプリを作ってみた。
ただ、XcodeとSwiftのバージョンが新しいのに、実機で持っていたのが「iOS9.3.5」だった。仮想デバイスですら「iOS10」なのにである。デバッグしてみると、悲しいことにいろんなところで調べたことが古くて使えないよ!と言われ、困り果て、何とかして両方使えるように共存できないか探ってみたら、何と似たようなことで困っていた人が記事にしてくれていたので、何とか助かった。
調べても、問題が解決しない記事が多かったので、今回自分でまとめることにした。備忘録。
何が問題?
「iOS10以降」と「それ以下」の違いが大きく関わっている。
・CoreDataの宣言の仕方が割と違う。
→ そもそも、AppDelegateで宣言されているものが違うので、使えるメソッドも違う。・AppDelegateで宣言されているものが違う
→ 前述したように、宣言されているものが違うので、Contextの宣言方法もまるで違う。※因みに、古いXcodeで作ればいいやんと思ってやってみたけど、macOS Mojaveでは、うまくXcodeの共存(つまり、Xcodeのバージョンが古いのと新しいのを両方とも置くこと)ができない。起動時にエラーが出る。(StackOverFlowでも探したけど、みんな同じことが起こって諦めている感じだった。)
対応方法
ここでやりたいのは「共存」。どっちでも使えるようにしたいと考え、以下のように対応した。
- 古いXcodeで宣言されているコードを、新しい方に移植する。
無ければ入れれば良い話で、そこからメソッドを呼び出せばいい。
古いバージョンのXcodeで宣言されているAppDelegateのメソッドを、新しいXcodeのAppDelegateに記述すれば、古い方からもそのバージョンにあったContextを呼び出せるという考え。
今回作ったアプリ
AppDelegate
AppDelegate.swiftimport UIKit import CoreData @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { /*ここまでは、新しいXCodeでは自動的に定義されている。 ------------------------------------------------------------------- ここから下が、古いXcodeからの移植 */ lazy var applicationDocumentsDirectory: NSURL = { let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) return urls[urls.count-1] as NSURL }() lazy var managedObjectModel: NSManagedObjectModel = { // The managed object model for the application. This property is not optional. It is a fatal error for the application not to be able to find and load its model. //forResource: には自分のAppの名前を入れる。 let modelURL = Bundle.main.url(forResource: "MyAppName", withExtension: "momd")! return NSManagedObjectModel(contentsOf: modelURL)! }() lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = { // The persistent store coordinator for the application. This implementation creates and returns a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail. // Create the coordinator and store let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel) //appendingPathComponentの引数には、データベース名.sqlite let url = self.applicationDocumentsDirectory.appendingPathComponent("MyDB.sqlite") var failureReason = "There was an error creating or loading the application's saved data." do { try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: nil) } catch { // Report any error we got. var dict = [String: AnyObject]() dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data" as AnyObject? dict[NSLocalizedFailureReasonErrorKey] = failureReason as AnyObject? dict[NSUnderlyingErrorKey] = error as NSError let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict) // Replace this with code to handle the error appropriately. // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. NSLog("Unresolved error \(wrappedError), \(wrappedError.userInfo)") abort() } return coordinator }() lazy var managedObjectContext: NSManagedObjectContext = { // Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail. let coordinator = self.persistentStoreCoordinator var managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType) managedObjectContext.persistentStoreCoordinator = coordinator return managedObjectContext }() /*ここまでをまずは移植する。 ------------------------------------------------------------------------ */ // MARK: - Core Data Saving support @available(iOS 10.0, *) func saveContext () { let context = persistentContainer.viewContext if context.hasChanges { do { try context.save() } catch { // Replace this implementation with code to handle the error appropriately. // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. let nserror = error as NSError fatalError("Unresolved error \(nserror), \(nserror.userInfo)") } } } //iOS9以下のCoreData_Saveメソッド これも移植したもの。 func saveContext9 () { if managedObjectContext.hasChanges { do { try managedObjectContext.save() } catch { // Replace this implementation with code to handle the error appropriately. // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. let nserror = error as NSError NSLog("Unresolved error \(nserror), \(nserror.userInfo)") abort() } } } }Contextの宣言
ViewController.swiftfunc getData(){ //ここでiOSのバージョンによって処理が変わる。 if #available(iOS 10.0, *) { //Contextの宣言(iOS10) let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext do{ sample = try context.fetch(Sample.fetchRequest()) }catch{ print("Error") } }else{ //Contextの宣言(iOS9) let context = (UIApplication.shared.delegate as! AppDelegate).managedObjectContext do{ sample = try context.fetch(Sample.fetchRequest()) }catch{ print("Error") } } }CoreData-Save編-
Add.swiftif #available(iOS 10.0, *) { let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext let defaultAction:UIAlertAction = UIAlertAction(title: "OK!", style: UIAlertAction.Style.default) { (action: UIAlertAction!) -> Void in let sample = Sample(context: context) sample.id = Int32(self.AutoIncrements()) sample.name = self.nameField.text sample.mail = self.mailField.text sample.pass = self.passField.text (UIApplication.shared.delegate as! AppDelegate).saveContext() self.navigationController!.popViewController(animated: true) } alert.addAction(defaultAction) } else { // Fallback on earlier versions let context9:NSManagedObjectContext = (UIApplication.shared.delegate as! AppDelegate).managedObjectContext let defaultAction:UIAlertAction = UIAlertAction(title: "OK!", style: UIAlertAction.Style.default) { (action: UIAlertAction!) -> Void in //ここからが新しいXcodeと違うところ let entity = NSEntityDescription.entity(forEntityName: "Sample", in: context9) let sample = NSManagedObject(entity: entity!, insertInto: context9) as! Sample sample.id = Int32(self.AutoIncrements()) sample.name = self.nameField.text sample.mail = self.mailField.text sample.pass = self.passField.text (UIApplication.shared.delegate as! AppDelegate).saveContext9() self.navigationController!.popViewController(animated: true) } alert.addAction(defaultAction) }終わりに
まだまだ記録できていないところはあるけれど、とりあえずここまで。
暇なときに追記していきます。参考サイト
-- xcode8でCoreData使うときにiOS9以前も対応に含めるときに --
https://qiita.com/fujiwarawataru/items/12903b6e8fe42d6464f2ここでヒントをもらいました。@fujiwarawataruさん。この記事を書いていただき感謝です。助かりました。
- 投稿日:2019-10-08T22:08:39+09:00
SwiftUI(View)とUIKit(UIView)と同じ画面にだしてsinkさせてみる
今回試してみたこと
今回はUIKitで作成したViewControllorとSwiftUIで作成したViewを一緒に表示させることに挑戦してみたいと思います。
おそらく新規プロジェクトでなければ、SwiftUIに移行することになったときに必ずやることになると思ったからです。
図にするとこんな感じ
とりあえず共有するデータを定義
final class MyData: ObservableObject { @Published var name: String = "" }そして、SwiftUIでViewを定義
struct TestView: View { @EnvironmentObject var myData: MyData var body: some View { HStack { Text(myData.name) .frame(minWidth: 100) Button(action: { self.myData.name = Int(arc4random_uniform(50000)).description }) { Text("タップして乱数生成") }.frame(minWidth: 100) } } }frameのminWidthやminHeightはよく使うことになりそうです。
これでボタンをタップしたら乱数を生成してTextで表示させるシンプルなViewが出来ました。
こんな感じ
次はUIViewを作る
class MyTestUIView: UIView { /// 共有するデータ var data: MyData /// 今回はコードでviewを生成します。MyDataをこのときに渡してます。 init(frame: CGRect, data: MyData) { self.data = data super.init(frame: frame) // UIKitで生成したViewをわかりやすくするためにyellowにしました backgroundColor = UIColor.yellow // textFieldに値を反映し、修正も行います let textField = UITextField(frame: CGRect(x: 100, y: 100, width: 100, height: 100)) addSubview(textField) textField.borderStyle = .roundedRect // (1) textFieldからのデータInputを処理する NotificationCenter.default.addObserver(forName: UITextField.textDidChangeNotification, object: nil, queue: OperationQueue.main) { notification in guard let text = (notification.object as? UITextField)?.text else { return } data.name = text } // (2) SwiftUIからInputが発生したときにTextFieldにも反映させる _ = data.$name.receive(on: DispatchQueue.main).sink() { textField.text = $0 } } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } }確認するところは2点あります
(1) textFieldからのデータInputを処理する
NotificationCenter.default.addObserver(forName: UITextField.textDidChangeNotification, object: nil, queue: OperationQueue.main) { notification in guard let text = (notification.object as? UITextField)?.text else { return } data.name = text }TextFieldの通知を活用してtextFieldの入力が発生したときのイベントをハンドリングしてみました。
textFieldのtextをdata.nameに代入します。もうこれでSwiftUIで定義したViewに反映されます。
HStack { Text(myData.name) .frame(minWidth: 100)こちらの
myData.name
は@Published
のおかげてデータが変更されたらTextが即時に更新されます(2) SwiftUIからInputが発生したときにTextFieldにも反映させる
_ = data.$name.receive(on: DispatchQueue.main).sink() { textField.text = $0 }
@Published
のnameの変更をmainスレッドでreceiveして、sinkでクロージャーを登録してデータが変わったことを知らせてもらっています。
とりあえずやってみたいことは出来ました。
シンプルにしたかったのでtextFieldの変更が入るたびにデータが更新されtextFieldもまた更新されるといった、無駄なことをやってるソースコードにも見えますがw
例えばメモ帳アプリで
上部はUIKItのカレンダーライブラリーで暦の表示
下部はSwiftUIで選択された日付のデータを表示
presentされた画面でイベントが登録されたら両方に反映とかがあり得るのかな?
他の書き方でも似たようなことは出来ると思うのでなにかあればぜひコメントもお願いします!
- 投稿日:2019-10-08T20:44:06+09:00
アプリのCI/CD関連サービス有名どころ調べてみた(簡易版)
Yoki(@enyyokii)と申します。
今回はCI/CD関連サービスの有名なものを調べてみたので簡単にまとめてみました。
AppCenterが思ったより便利でした ☺️CI/CDサービス
役割 サービス名 機能 対象 料金 備考 ビルドツール fastlane ビルド自動化
テスト実行
証明書の管理
スクリーンショットの生成iOS: 主流
Android: △
全ての機能が使用できるわけではない
google playへのリリースやスクリーンショットの保存が可能
しかし、gradleがビルドツールとしての役割を担うため、fastlaneそのものへのありがたみは少ない。無料 アプリのタスク実行ツール
BitriseなどのCIツールと併用しローカルで実行しないように生産効率あげることが多いCI/CD App Center ビルド時間:
(無料)240 ビルド時間 (分)/月
ビルドごとに 30 分まで
テスト:無制限の起動テストAndroid
iOS
macOS
Windows(UWP)
Xamarin基本無料
無制限のビルド時間+複数のビルド同時実行: ¥4480円/月
UIテストを好きな構成で実行: ¥11088円/月ViewController2つのWebView表示する簡易アプリでビルド時間は10分程(初回は時間かかるかも)
これはすぐに超えそうなので¥4480/月は契約した方がいいかも?CI/CD Bitrise 設定方法: GUI(WEB)、YAML
ビルド時間制限約8分
同時実行数:
(無料)x1
(有料)x2〜x18Android
iOS
Xamarin無料: 0
有料: $40/month〜(年契約もあり)
アプリに特化している
iOSDCへ参加するなど日本市場へ注力している。日本語記事も見つけやすい。CI/CD Circle CI 設定方法: YAML モバイルアプリ
WebアプリLinuxでのビルド: コンテナ数1 → 無料 ,
コンテナ数増やすごとに$50ずつかかる
macOSでのビルド: $39/month〜ビルドコンテナメモリ上限: 4G
2コンテナ以上利用する場合は、ビルド時間の制限はなくなる
アカウント登録はgithub連携必須
AppCenterのみ試してみたが、30分もあればpushをトリガーに、ビルド、単体テスト、配布までを用意に自動化できる。ストアへのアップロードもできる。
プロダクトをまたいでテストアプリを触るケースがあるので配布ツールは統一したいかも
iOSの場合、ワークフローはfastlaneにまとめておき、CI/CDツールでfastfileを実行するようにしておけばツールを変更した際の移行が楽(ZOZOの事例より)
複数のプラットフォームで同じようにワークフローを管理したいのであれば、Bitriseは選択肢から外れそう。
参考
0から始めるiOS自動化
AndroidのCIサービスをCircleCIからBitriseへ移行しました
iOSで構築しているCIのWorkflow紹介
Visual Studio App Centerで始めるCI/CD(iOS)
署名なしでiOSアプリのビルド&単体テスト〜AppCenter編〜
- 投稿日:2019-10-08T20:40:28+09:00
iOS13 UISegmentControlのBeforeAfterを見てみる
概要
iOS13からUISegmentedControlの見た目と動きが大きく変更になりました。
これまではButtonが複数あるタブっぽい動きでしたが、UISwitchのような動きに変わりましたね。
iOS13では他の新機能が目立って取り上げられていたので、UIKitの見た目変更があると全く知らなくて
開発中アプリの動作確認をしていたら、激変していてびっくりしました汗
実装する上でも変更点があったので備忘録としてまとめていきます。変更点
iOS7からはUISegmentControlの色の指定は
tintColor
でしていましたが、
iOS13以降はこれが使えなく、代わりにselectedSegmentTintColorが追加されました。
機能的には全く同じ。
そう、プロパティ名が長くなっただけ←どちらもアプリで対応する場合は
available
を使用してバージョン管理が必要です。SampleViewController.swiftimport UIKit class SampleViewController: UIViewController { @IBOutlet weak var segmentControl: UISegmentedControl! override func viewDidLoad() { super.viewDidLoad() // 背景色 segmentControl.backgroundColor = .white // ベースカラー if #available(iOS 13.0, *) { segmentControl.selectedSegmentTintColor = .red } else { segmentControl.tintColor = .red } // 通常時の文字色 segmentControl.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.red], for: .normal) // 選択時の文字色 segmentControl.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.white], for: .selected) } }ちなみに、背景色に白色を指定すると影がついたようなグレーになってしまいます。。
これはそういう仕様のようで色々頑張ってみましたが背景を完全な白にできませんでした。。
どうしてもしたい場合は画像を背景にするしかないのかな?汗また、iOS12以前のSegmentControlのように外枠をつけてみました。
SampleViewController.swift// 上記のコードに追記 segmentControl.layer.borderColor = UIColor.red.cgColor segmentControl.layer.borderWidth = 1 segmentControl.layer.cornerRadius = 4ちょっと外枠と内側のタブの間に隙間ができるんですね。なるほど。。
iOS12以前のものはパッと見変化がないですが、cornerRadius
の値によって角丸のところが色が固まっているみたいになりました。。
下手に外枠はつけない方が良さそうですかね汗参考
- 投稿日:2019-10-08T19:33:42+09:00
xcode11でシミュレーターに機種を追加する方法
- 投稿日:2019-10-08T14:42:29+09:00
Flutterウィークリー #78
Flutterウィークリーとは?
FlutterファンによるFlutterファンのためのニュースレター
https://flutterweekly.net/この記事は#78の日本語訳です
https://mailchi.mp/flutterweekly/flutter-weekly-78※Google翻訳を使って自動翻訳を行っています。翻訳に問題がある箇所を発見しましたら編集リクエストを送っていただければ幸いです。
読み物&チュートリアル
Flutter内部
https://www.didierboelens.com/2019/09/flutter-internals/
これは必読です。 Didier Boelensは、 Flutter描画サイクルの内部が詳細にどのように機能するかを説明します。ListViewで無限スクロールを実装する
https://codinglatte.com/posts/flutter/listview-infinite-scrolling-in-flutter/
Maina Wycliffeは、一番下までスクロールするときに、より多くのコンテンツをロードするリストを作成する方法を示します。Flutterメッセージングアプリを構築する-パートI:プロジェクトの構造
https://medium.com/@MiBLT/building-a-messaging-app-in-flutter-part-i-project-structure-7d6db38783a5
Miguel Beltranは、顧客がキャンセルし、共有することに同意し、そこで行われた作業の文書化を開始した後、ほぼ完全なプロジェクトを共有します。Flutterアクセシビリティ対応アプリの開発とテスト
https://medium.com/flutter-community/developing-and-testing-accessible-app-in-flutter-1dc1d33c7eea
Flutterアプリでのアクセシビリティの実装に取り組んだ後、Darshan Kawarは彼がそれについて学んだことを説明します。プロバイダーを使用してFlutter動的テーマを作成する方法
Divyanshu Bhargavaによるこのチュートリアルで、Providerを使用して実行時にアプリのテーマを変更することを学びます。GameKitとGoogleプレイサービスを統合する方法
Abedalkareem Omreyhが、 FlutterアプリでiOS用GameKitとAndroid用Goople Play Servicesを使用する方法を説明します。BackdropFilterとImageFilterを使用してFlutterぼかし効果を作成する方法
Bui Minh Trietによるこのチュートリアルで、アプリにぼかし効果を適用する方法を学びます。Flutterプロシージャルテクスチャ
https://medium.com/@av.codes/procedural-textures-with-flutter-efcf546cd1fc
あなたは芸術に興味がありますか? Ivan CherepanovはFlutter Perlinノイズのような手続き型テクスチャを作成する方法を説明します。ダイアグラムでのプロバイダーの理解—パート1:値の提供
Joseph T. Lappによるアプリでのプロバイダーの使用に関する3部構成の記事。記事でリンクされているパート2と3。FlutterがNubankでのモバイル開発のスケーリングに役立つと考える理由
https://medium.com/building-nubank/https-medium-com-freire-why-nubank-chose-flutter-61b80b568772
NubankのAlexandre Freireは、開発プラットフォームを採用するために行ったプロセスについてこの優れた記事を書きました。ネタバレ: Flutter勝ちます。FlutterでバーコードとQRコードを操作する
https://medium.com/flutter-community/working-with-barcodes-and-qr-codes-in-flutter-fbb3a2d4ede7
FlutterアプリからのバーコードとQRコードのスキャンに関するRitesh Sharmaのチュートリアル。ビデオ&メディア
プロバイダーでのデータ管理
https://www.youtube.com/watch?v=nZvELm3QnXc&feature=youtu.be
Raja Yoganによるこのビデオチュートリアルで、プロバイダーでデータを処理する方法を学びます。Hive( Flutter Tutorial)– Pure Dart軽量で高速なNoSQLデータベース
https://www.youtube.com/watch?v=R1GSrrItqUs&feature=youtu.be
軽量でありながら強力なデータベースであるHiveの使用に関するチュートリアル。開発が容易で、デバイス上でも高速に実行されます。ListTile( Flutter Widget of the Week)
https://www.youtube.com/watch?v=l8dj0yPBvgQ&list=PLjxrf2q8roU23XGwz3Km7sQZFTdB996iG&index=55&t=0s
ListTileはリストアイテムのマテリアルデザインパターンを実装するため、リストアイテムの内容について心配する必要はありません。Flutterデスクトップアプリを作成する方法
https://www.youtube.com/watch?v=cMK6qxmTdaM
このビデオでは、LinuxおよびWindowsでFlutterを実行する方法について説明します。現在の状態は何ですか?試してみたい場合は何を達成できますか?BLoCパターンでFlutterコードを設計する方法
https://www.youtube.com/watch?v=NCJZ_T8FiUo
BLoCパターンに従って、可能な限り最も簡単かつ効率的な方法でFlutterコードを設計する方法を学びます。ESP32 |フラッター| BLE-ダストセンサー、シンプルな空気モニターアプリ(グラフ付き)
https://www.youtube.com/watch?v=w6jo2kJanqU
ESP32を使用してFlutterアプリケーションを作成し、BLEを介してダストセンサーデータを取得する方法に関するチュートリアル。ライブラリ&コード
バージョン2.0 |ムーア
https://moor.simonbinder.eu/v2/
Moorのバージョン2.0がリリースされました。その完全なウェブサイトをチェックして、それを最大限に活用する方法を学んでください。ミラド・アカリエ/ form_field_validator
https://github.com/Milad-Akarie/form_field_validator
一般的な検証オプションを提供する簡単なフラッターフォームフィールドバリデーター。
timfreiheit / r_flutte
https://github.com/timfreiheit/r_flutter
すべてのフラッターアセット用に自動生成された定数。
niklas-8 / RemoteFiles
https://github.com/niklas-8/RemoteFiles
AndroidおよびiOS用のオープンソースSFTPクライアントTimoteohss / slide_button
https://github.com/Timoteohss/slide_button
フラッターを確認するスワイプボタン
mmcc007 /シルフ
https://github.com/mmcc007/sylph
クラウド内の実際のデバイスでFlutter統合テストを実行します。
- 投稿日:2019-10-08T11:06:38+09:00
[swift]トルツメ機能を実装する
はじめに
Web画面とかでよくあるトルツメ機能が欲しかったのですがUIKITなどにはもちろんなく、一から手作りしたので共有します。
画面のイメージ
コード
ViewController.swiftimport UIKit class ViewController: UIViewController { // トルツメ設定 let horizontalStackView:UIStackView = UIStackView() let verticalStackView:UIStackView = UIStackView() override func viewDidLoad() { super.viewDidLoad() // 画面の大きさを取得 let screenWidth = Int( UIScreen.main.bounds.size.width); let screenHeight = Int( UIScreen.main.bounds.size.height); print("screenWidth=\(screenWidth)") print("screenHeight=\(screenHeight)") // 横用StackView view.addSubview(horizontalStackView) horizontalStackView.translatesAutoresizingMaskIntoConstraints = false // オートレイアウトオフ horizontalStackView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true // 上のラベル 横並びにするためにダミーのViewも作成する let label = UIView() label.backgroundColor = UIColor.clear label.translatesAutoresizingMaskIntoConstraints = false label.widthAnchor.constraint(equalToConstant: CGFloat(screenWidth/4)).isActive = true label.heightAnchor.constraint(equalToConstant: 40).isActive = true let label2 = UIView() label2.backgroundColor = UIColor.clear label2.translatesAutoresizingMaskIntoConstraints = false label2.widthAnchor.constraint(equalToConstant: CGFloat(screenWidth/4)).isActive = true label2.heightAnchor.constraint(equalToConstant: 40).isActive = true let label3 = UIView() label3.backgroundColor = UIColor.clear label3.translatesAutoresizingMaskIntoConstraints = false label3.widthAnchor.constraint(equalToConstant: CGFloat(screenWidth/4)).isActive = true label3.heightAnchor.constraint(equalToConstant: 40).isActive = true let label4 = UIButton() label4.backgroundColor = UIColor.gray label4.translatesAutoresizingMaskIntoConstraints = false label4.widthAnchor.constraint(equalToConstant: CGFloat(screenWidth/4)).isActive = true label4.heightAnchor.constraint(equalToConstant: 40).isActive = true // 画像 let image:UIImage = UIImage(named:"menu")! let imageView = UIImageView(image:image) label4.addSubview(imageView) // addしてからじゃないと制約追加できないので注意 imageView.translatesAutoresizingMaskIntoConstraints = false imageView.centerXAnchor.constraint(equalTo: label4.centerXAnchor).isActive = true imageView.centerYAnchor.constraint(equalTo: label4.centerYAnchor).isActive = true imageView.contentMode = UIView.ContentMode.scaleAspectFit // 縦用StackView view.addSubview(verticalStackView) verticalStackView.translatesAutoresizingMaskIntoConstraints = false verticalStackView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true verticalStackView.axis = NSLayoutConstraint.Axis.vertical // 縦並び verticalStackView.alpha = 0.8 // メインView let mainView = UIView() mainView.backgroundColor = UIColor.gray mainView.translatesAutoresizingMaskIntoConstraints = false mainView.widthAnchor.constraint(equalToConstant: CGFloat(screenWidth)).isActive = true mainView.heightAnchor.constraint(equalToConstant: CGFloat(screenHeight/10)).isActive = true // 項目AのLabel let itemName = UILabel() itemName.textColor = .white itemName.text = "項目A: テスト" mainView.addSubview(itemName) itemName.translatesAutoresizingMaskIntoConstraints = false itemName.centerXAnchor.constraint(equalTo: mainView.centerXAnchor, constant: 15).isActive = true itemName.centerYAnchor.constraint(equalTo: mainView.centerYAnchor, constant: CGFloat(screenHeight/60)).isActive = true itemName.widthAnchor.constraint(equalToConstant: CGFloat(screenWidth)).isActive = true itemName.font = UIFont.boldSystemFont(ofSize: 21) // 項目BのLabel let torishoName = UILabel() torishoName.textColor = .white torishoName.text = "項目B: test" mainView.addSubview(torishoName) torishoName.translatesAutoresizingMaskIntoConstraints = false torishoName.centerXAnchor.constraint(equalTo: mainView.centerXAnchor, constant: 15).isActive = true torishoName.centerYAnchor.constraint(equalTo: mainView.centerYAnchor, constant: -CGFloat(screenHeight/60)).isActive = true torishoName.widthAnchor.constraint(equalToConstant: CGFloat(screenWidth)).isActive = true torishoName.font = UIFont.boldSystemFont(ofSize: 21) horizontalStackView.addArrangedSubview(label) horizontalStackView.addArrangedSubview(label2) horizontalStackView.addArrangedSubview(label3) horizontalStackView.addArrangedSubview(label4) verticalStackView.addArrangedSubview(horizontalStackView) verticalStackView.addArrangedSubview(mainView) // 表示時はhiddenとする verticalStackView.arrangedSubviews[1].isHidden = true // ボタンを押した時 label4.addTarget(self, action: #selector(pushButton), for: .touchUpInside) } // ボタン押したとき @objc func pushButton(sender: UIButton){ //ここにViewを隠したり表したりする処理を入れる verticalStackView.arrangedSubviews[1].isHidden.toggle() verticalStackView.layoutIfNeeded() } }StackViewを組み合わせて実現しました。
もっといい方法あるよ!などあったら教えてください。
- 投稿日:2019-10-08T09:53:03+09:00
[iOS]証明書がなかったらどうなるのか
なぜこの記事を書こうと思ったか
・iOSエンジニアなのに証明書の存在理由がいまいち分からなかったから
・証明書の扱いが煩雑でムカムカしていたからどういう人に向けて書いているのか
・証明書の恩恵を知らずに扱っている人
・ざっくりと証明書の存在理由について知りたい人二行要約
・証明書技術がないとなりすましや第三者から簡単に攻撃されてしまう
・証明書を取り巻く暗号技術を用いることによって、安心安全に二者間で情報の受け渡しが可能になる
証明書がない世界
なりすまし
改ざん
盗聴
事後否認
etc
信用が成り立たず、誰もインターネットに参加しない...
証明書の仕組み
サイバートラスト社に代表される信頼された認証局が、情報通信先のサーバのサイト運営組織が実在していることを証明し、WebブラウザとWebサーバ間(サーバ同士でも可能)でSSL(Secure Socket Layer)暗号化通信を行うための電子証明書です。
引用元: SureServerつまり信頼できる第三者機関が認めた身元証明
それがあるから安心して見ず知らずの第三者とネットワークを通してやりとりできる
iOSアプリ開発の場合、Appleが信頼できる第三者機関として証明書発行を行ってiru
結論
証明書があるからこそ開発者は安心してアプリ配信ができるし、ユーザーも安心してアプリを使用できることがなんとなく理解できました。
今回の記事では、技術的なことは相当端折ったのですが、なんとなく雰囲気を理解してもらえれば幸いです。余談
暗号技術入門をマインドマップでまとめています。
証明書を構成する暗号技術についてもまとめているので、深いところまで気になる方はこちらどうそ。
暗号技術マインドマップ
- 投稿日:2019-10-08T09:11:58+09:00
【Swift】 「タブを閉じる」ボタンの追加(自作タブブラウザアプリ開発)
このエントリ(【Swift】 タブブラウザライクに、任意のボタンを押した際に対応するViewを最前面に表示する)の続きとして、各タブに「タブを閉じるボタン」をつけました。
開発環境
端末:MacBook Pro/MacOS 10.14.5(Mojave)
Xcode:10.2.1
Swift:5やったこと(ポイント)
①各タブに「タブを閉じる」ボタンを追加
実装
画面イメージ
各タブの左に、赤い「×」でボタンを付けています。
3つのタブが表示された状態
「tabNumber2」以外を閉じた状態 見た目では分からないですが、「×」ボタンを押すと、対応するタブボタンとそれに紐づいているWKWebViewも消えています。
あと、消したタブの右側にタブが存在する場合、ちゃんと左に詰められるようにしています。ソースコード
冒頭にリンクを貼ったエントリから、修正した点は大きく2つです。
①タブに「閉じる(×)」ボタンを追加表示させる
②「閉じる」ボタン押下時の処理を追加するそれ以外の部分を割愛すると、以下のようなコードで実現しています。
ViewController.swiftimport UIKit import WebKit class ViewController: UIViewController, WKNavigationDelegate, WKUIDelegate, UIScrollViewDelegate { ... // 前回のエントリに貼ったコードを参照。 @objc func closeTab(sender: UIButton) { // webViewを消す let viewTag = sender.tag + 9000 let targetView = view.viewWithTag(viewTag) as! WKWebView targetView.removeFromSuperview() // タブボタンを消す let btnTabTag = sender.tag - 1000 let targetBtn = view.viewWithTag(btnTabTag) as! UIButton targetBtn.removeFromSuperview() // タブボタンにひっついていた「閉じる」ボタン自体も消す let btnCloseTabTag = sender.tag let targetBtnCloseTab = view.viewWithTag(btnCloseTabTag) as! UIButton targetBtnCloseTab.removeFromSuperview() // UIScrollViewの中身を残りのタブで配置しなおす originX = 0 tabScrollView.frame = CGRect.init(x: 0, y: 44, width: viewWidth, height: 50) tabScrollView.contentSize = CGSize.init(width: 0, height: 50) for i in 1...tagNumber { if (view.viewWithTag(i) != nil) { let targetButton = view.viewWithTag(i) as! UIButton let targetButtonWidth:CGFloat = 200 let targetButtonHeight:CGFloat = tabScrollView.frame.height // タブボタンの見た目設定 targetButton.tag = i targetButton.layer.cornerRadius = 3.0 targetButton.layer.borderWidth = 1.5 targetButton.layer.borderColor = UIColor.init(red: 87/255, green: 144/255, blue: 40/255, alpha: 1).cgColor targetButton.backgroundColor = .white targetButton.setTitleColor(UIColor.init(red: 118/255, green: 197/255, blue: 57/255, alpha: 1), for: .normal) targetButton.titleLabel?.font = UIFont.systemFont(ofSize: 15) targetButton.addTarget(self, action: #selector(showView(sender: )), for: .touchUpInside) targetButton.frame = CGRect(x: originX, y: 0, width: targetButtonWidth, height: targetButtonHeight) // タブを閉じるボタン let closeTagNumber = i + 1000 let targetCloseButton = view.viewWithTag(closeTagNumber) as! UIButton let targetCloseButtonWidth:CGFloat = 30 let targetCloseButtonHeight:CGFloat = 30 targetCloseButton.tag = closeTagNumber targetCloseButton.addTarget(self, action: #selector(closeTab(sender:)), for: .touchUpInside) targetCloseButton.frame = CGRect.init(x: originX + 160, y: 10, width: targetCloseButtonWidth, height: targetCloseButtonHeight) // スクロールバーの中のタブボタンの表示位置をずらす制御 originX += targetButtonWidth tabScrollView.addSubview(targetButton) tabScrollView.addSubview(targetCloseButton) tabScrollView.contentSize = CGSize(width: originX, height: targetButtonHeight) print("originX = " + originX.description) view.addSubview(tabScrollView) } } } func addTab(url: URL) { ... // 前回のエントリに貼ったコードを参照。 // タブを閉じるボタン let btnCloseTab = UIButton() let btnCloseTabWidth:CGFloat = 30 let btnCloseTabHeight:CGFloat = 30 btnCloseTab.tag = tagNumber + 1000 btnCloseTab.titleLabel?.textAlignment = .center btnCloseTab.setTitle("×", for: .normal) btnCloseTab.setTitleColor(.red, for: .normal) btnCloseTab.addTarget(self, action: #selector(closeTab(sender:)), for: .touchUpInside) btnCloseTab.frame = CGRect.init(x: originX + 160, y: 10, width: btnCloseTabWidth, height: btnCloseTabHeight) // スクロールバーの中のタブボタンの表示位置をずらす制御 originX += tabButtonWidth // タブボタンと、閉じるボタンをUIScrollViewに追加 tabScrollView.addSubview(tabButton) tabScrollView.addSubview(btnCloseTab) tabScrollView.contentSize = CGSize(width: originX, height: tabButtonHeight) view.addSubview(tabScrollView) view.addSubview(webView) ... }一応「閉じる」機能を実装できました。
他の良いやり方が思いつかないですが、なんかビミョーな感じはしてます。補足
細かいトコですが、前回のエントリではタブボタンの文字をタブ中央に配置していたんですが、「閉じる」ボタンの追加にあたり、左寄せにしました。
しかし、そのやり方が
UIButton.titleLabel.textAlignment = .left
ではなく、
UIButton.contentHorizontalAlignment = .left
だったので、一応補足まで。let tabButton = UIButton() let tabButtonWidth:CGFloat = 200 let tabButtonHeight:CGFloat = tabScrollView.frame.height // タブボタンの見た目設定 tabButton.tag = tagNumber // この設定はあまり意味がなかった。 // tabButton.titleLabel?.textAlignment = .center tabButton.contentHorizontalAlignment = .left感想など
「タブを閉じる」はできたので、次は「今開いてるタブはどれか」を示すために色を変えていたのを、タブを閉じた時にどう制御するかを考えていきます。
あと、実際動かしたいWebサービスでは、target="hoge"
のように開くタブが指定されているリンクがあるので、それをきちんと制御してあげるにはどうしたらいいかが課題かなと思っています。引き続き頑張ります。
以上です。