20210915のiOSに関する記事は6件です。

[Swift]修飾子のLazyとは?

はじめに 最近はコードでUIを作るのにハマっています。その中で、Lazyが必要な場面が出てきたので備忘録的にまとめておきます。 どんなときに使えるの? 結論、一つの用法として、コードでUIを作っていくときなどに使えることがあります。 例えば、UIButtonであれば以下のコードのように書けます。 このコードは”Lazy”を付けなくても問題なくUIButtonを生成することができます。 let button: UIButton = { let button = UIButton() button.setTitle("tap", for: .normal) button.backgroundColor = .blue return button }() では、いつ"Lazy"が必要になるのか、それはCollectionViewやTableViewでdelegate、dataSourceをselfに設定するときです。 "Lazy"をつけることで、delegateやdataSourceにselfを設定することができるようになります。 "Lazy"をつけないと怒られます。 この他にも、"Lazy"をつけることで自身のプロパティやメソッドにもアクセスすることが可能になります。 lazy var infoCollectionView: UICollectionView = { let layout = UICollectionViewFlowLayout() let cv = UICollectionView(frame: .zero, collectionViewLayout: layout) cv.delegate = self cv.dataSource = self cv.backgroundColor = .brown return cv }() もう少し掘り下げて"Lazy"について知りたい方は以下の記事が参考になると思います。 https://qiita.com/reo0612/items/c2421bca65d5723b1055 おわりに 最近夜に犬の散歩をするのにハマっています。おわり。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iPhone13を発表したAppleEventメモ

本日(2021/09/15)の Apple Event(iPhone13発表)のメモになります。 Apple TV+ Apple TV+ でのコンテンツ紹介 iPad(第9世代) iPadOS15搭載 A13Bionic CPUやカメラがアップグレード センターフレームに対応(今回発表した全機種) 329ドル(64GB)から 色はシルバーとグレイ 本日受付開始 販売は来週 iPad mini(第6世代) 色:パープル、ピンク、スターライト、スペースグレイ 画面は8.3インチに拡大 Touch IDが画面上部の電源ボタンに移動 物理的なホームボタンは廃止 コネクタはUSB-C カメラやオーディオもアップグレード 4K, 12MP, センターフレームに対応 横向きでのスピーカーに対応 Apple Pencilにも対応 本日受付開始 販売は来週 全てのラインナップで100%再生アルミニウムの筐体を使用 Apple Watch Series7 外枠が狭くなることでディスプレイサイズが20%広くなった 曲面ディスプレイのためにUIを更新して、新しい画面領域が用意された(※アプリ開発者には影響ありそう) 耐久性もアップ アップグレードされた充電アーキテクチャと高速充電USB-Cケーブル 今秋後半リリース Apple Fitness+ これまで6ヶ国に対応していたが、今後+15ヶ国追加される(日本はまだ未対応) 新しいワークアウトの紹介 SharePlayを使い Fitness+ の GroupWorkouts でグループ共有可能 iPhone13 色:ピンク、プルー、ミッドナイト、スターライト、Product RED ノッチが20%縮小 ※わずかにノッチの高さが増えている情報もあるが、少なくともセーフエリアの高さは44point以下にはなってないだろう。(アプリ開発者としては重要な時がある) →Xcode13RC のシミュレータで試したら、iPhone13, Pro 共に safeAreaInsets.top は iPhone12 と同じ 47 point でした A15Bionic 6コア(2高性能 + 4高効率) GPUは4コア Neural Engineは16コア カメラもアップグレード シネマティックモード 動画撮影でプロ並みのピント送りが可能 被写体が向いている方向に自動でピントが合う バッテリーはiPhone12よりも 2.5h 長持ち Smart Data mode で5Gが不要な時はLTEを使う プライバシーに配慮して、Siriはローカルでのみ使用 インテリジェントトラッキング防止機能 メールのプライバシー保護機能 iPhone13Pro 新色:シエラブルー フロントのTrueDepthカメラが20%縮小し、ノッチも縮小 A15Bionic GPUは5コア ProMotion 状況に合わせてリフレッシュレートを10Hz~120Hzまで自動調整 スクロールなどの動きに合わせて自動でリフレッシュレートが調整される 各カメラ性能も大幅アップ 天体撮影などにも利用可能 マクロ写真撮影で2cmまで接近して撮影が可能 Photographic style で自分の好みを画像パイプラインに取り込める 撮影前に4つのデフォルトから選べる ビデオには Cinematic mode 追加され、ピント送りが可能 年末までにProResビデオに対応予定 バッテリーはiPhone12Proよりも 1.5h 長持ち 9/17 受付開始 9/24 販売 iOS15リリース日 iOS15のリリースは日本時間9月21日 ただし、私はビデオの中でその情報を見つけられなかった。(単純に見過ごしたのかもしれない) アップル、iOS 15を9月21日に配信 iPadOS 15 / watchOS 8も同日 - Engadget 日本版 ちなみに私はiPadにベータ版のiPadOS15を入れているが、9/15時点では iPadOS 15.0 RC(19A344)となる。 「システムバージョン」には「15.0」としか表示されないので正式版かと焦ったがRCだった。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Flutterで手書きを実装する

実装する機能 今回実装する機能は、 自由に画面をなぞると特定の範囲内で手書きで絵が描けるようにする ひとつ戻る(undo)ひとつ進む(redo)ボタンの実装 全部消すボタンの実装 になります。類似した機能が入ったアプリだと消しゴムが入っていますが、今回のアプリではそこまで必要性を感じなかったため実装しません。 パッケージ・バージョン Flutterのバージョンは2.2.0です。 使用するパッケージは状態管理のためにhooks_riverpodとstatenotifier、freezedを使います。 特にそれ以外のパッケージは使用しません。 バージョンは以下の通りです。 pubspec.yaml dependencies: flutter_hooks: ^0.17.0 freezed_annotation: hooks_riverpod: ^0.14.0+4 dev_dependencies: build_runner: freezed: 手書きの実装 土台の作成 本題の実装になります。 今回の手書きの範囲は正方形にしたいので、画面中央に配置します。 draw_screen.dart class DrawScreen extends StatelessWidget { const DrawScreen({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Appbar'), ), body: Center( child: Container( width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.width, color: Colors.white, ), ), ); } } 画面をなぞった時に感知させたいため、GestureDetectorを使用します。 draw_screen.dart ... child: Container( width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.width, color: Colors.white, child: GestureDetector( onPanStart: (details) {}, onPanUpdate: (details) {}, onPanEnd: (details) {}, ), ), ... ペイントの状態を管理 次に、GestureDetectorのそれぞれの関数の引数から、なぞっているポジションが取得できるので、それを格納するためのStateNotifierProviderを追加します。 draw_controller.dart // ステート @freezed abstract class DrawState with _$DrawState { const factory DrawState({ @Default(<List<Offset>>[]) List<List<Offset>> paintList, @Default(<List<Offset>>[]) List<List<Offset>> undoList, @Default(false) bool isDrag, }) = _DrawState; } // コントローラー final drawController = StateNotifierProvider.autoDispose<DrawController, DrawState>( (ref) => DrawController()); class DrawController extends StateNotifier<DrawState> { DrawController() : super(const DrawState()); void undo() { // ひとつ戻る } void redo() { // ひとつ進む } void clear() { // 全消し } void addPaint() { // ペイント開始 } void updatePaint() { // ペイント中 } void endPaint() { // ペイント終了 } } paintListは 描画開始から描画終了までの線 = なぞったポジションの集まり = List<Offset> かつ、その線を何本もかけるので、その集まり = List<List<Offset>> となります。 undoListはundoをした際に、paintListの最後の要素を格納して、redoの際にpaintListに戻すためのものなので 、同様にList<List<Offset>>になります。 isDragは描画中かどうかの判別用ステータスで、描画中にundoやredoなどを使えないようにするためのものです。 ではそれぞれの関数を実装していきます。 draw_controller.dart void undo() { // 描画中か、undoできなかったら何もしない if (state.isDrag || !canUndo) { return; } // paintListの最後を取って、undoListに追加する final _last = state.paintList.last; state = state.copyWith( undoList: List.of(state.undoList)..add(_last), paintList: List.of(state.paintList)..removeLast(), ); } void redo() { // 描画中か、redoできなかったら何もしない if (state.isDrag || !canRedo) { return; } // undoListの最後を取って、paintListに追加する final _last = state.undoList.last; state = state.copyWith( undoList: List.of(state.undoList)..removeLast(), paintList: List.of(state.paintList)..add(_last), ); } void clear() { // 全ての要素を空にするだけ if (!state.isDrag) { state = state.copyWith(paintList: [], undoList: []); } } void addPaint(Offset startPoint) { if (!state.isDrag) { state = state.copyWith( isDrag: true, // 描画中に変更 paintList: List.of(state.paintList)..add([startPoint]), // 新たに開始地点を追加 undoList: const [], // 一つ進めるものがないはずなので空に(redoできないように) ); } } void updatePaint(Offset nextPoint) { // 最後の要素に進んだポジションを追加 if (state.isDrag) { final _paintList = List<List<Offset>>.of(state.paintList); final _offsetList = List<Offset>.of(state.paintList.last)..add(nextPoint); _paintList.last = _offsetList; state = state.copyWith(paintList: _paintList); } } // 描画終了 void endPaint() => state = state.copyWith(isDrag: false); GestureDetectorと上の関数を繋げます。 flutter_hooksを使用しているため、StatelessWidgetをHookWidgetに変更し、runApp()にProviderScope()を追加することを忘れないでください。 draw_screen.dart ... @override Widget build(BuildContext context) { final _state = useProvider(drawController); final _controller = useProvider(drawController.notifier); ... child: GestureDetector( onPanStart: (details) => _controller.addPaint(details.localPosition), onPanUpdate: (details) { _controller.updatePaint(details.localPosition); }, onPanEnd: (_) => _controller.endPaint(), child: CustomPaint(painter: Signature(_state, context)), ), ... 描画する機能の実装 なぞったポジションをもとに描画する必要があるため、カスタムペインターを使います。 signature.dart class Signature extends CustomPainter { Signature(this.state, this.context); final DrawState state; final BuildContext context; @override void paint(Canvas canvas, Size size) { const strokeWigth = 12.0; final paint = Paint() ..color = Colors.black ..strokeCap = StrokeCap.round ..strokeWidth = strokeWigth; for (final points in state.paintList) { // 一番最初にタップした地点に点を打つ // そうしないとタップして離しただけの時に描画されない canvas.drawRRect( RRect.fromRectAndRadius( Rect.fromCenter( center: points[0], width: strokeWigth, height: strokeWigth), const Radius.circular(strokeWigth), ), paint, ); // ひとかたまりの線の描画 for (var i = 0; i < points.length - 1; i++) { canvas.drawLine(points[i], points[i + 1], paint); } } } // paintListが変更されている時のみリビルド @override bool shouldRepaint(Signature oldDelegate) => oldDelegate.state.paintList != state.paintList; } これで一旦は描画できるようになっていると思います。 枠からはみ出さないよう修正 しかし、このままでは、範囲外まで描画できるようになっています。 要件を満たすように修正をしていきます 修正内容としては、枠を超えたら枠の端でとどまるようにします。 draw_screen.dart ... // ポジションの取得 Offset _getPosition(double length, Offset localPosition) { double _dx; double _dy; if (localPosition.dx < 0) { _dx = 0; } else if (localPosition.dx > length) { _dx = length; } else { _dx = localPosition.dx; } if (localPosition.dy < 0) { _dy = 0; } else if (localPosition.dy > length) { _dy = length; } else { _dy = localPosition.dy; } return Offset(_dx, _dy); } ... それと、onPanUpdateを次のように変更します onPanUpdate: (details) { _controller.updatePaint(_getPosition( MediaQuery.of(context).size.width, details.localPosition)); }, こうすることで、超えることがなくなったと思います。 undo,redo,全消しボタンの実装 最後にボタンの配置をします。 ボタンは左からundo, redo, 全消で配置します また、undo, redoできる時のみアクティブカラーにします。 draw_screen.dart Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ ElevatedButton( onPressed: _controller.undo, style: ElevatedButton.styleFrom( shape: const CircleBorder(), primary: _controller.canUndo ? Theme.of(context).accentColor : Colors.grey[200], onPrimary: Colors.white, padding: const EdgeInsets.all(10), ), child: const Icon(Icons.undo, size: 40), ), ElevatedButton( onPressed: _controller.redo, style: ElevatedButton.styleFrom( shape: const CircleBorder(), primary: _controller.canRedo ? Theme.of(context).accentColor : Colors.grey[200], padding: const EdgeInsets.all(10), onPrimary: Colors.white, ), child: const Icon(Icons.redo, size: 40), ), ElevatedButton( onPressed: _controller.clear, style: ElevatedButton.styleFrom( shape: const CircleBorder(), primary: Colors.red, padding: const EdgeInsets.all(10), onPrimary: Colors.white, ), child: const Icon(Icons.delete, size: 40), ), ], ), 以上になります!!! 完成図 コード全体 dart_screen.dart import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:test/draw_controller.dart'; class DrawScreen extends HookWidget { const DrawScreen({Key? key}) : super(key: key); @override Widget build(BuildContext context) { final _state = useProvider(drawController); final _controller = useProvider(drawController.notifier); return Scaffold( appBar: AppBar( title: const Text('Appbar'), ), body: Column( children: [ const Spacer(), Container( width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.width, color: Colors.white, child: GestureDetector( onPanStart: (details) => _controller.addPaint(details.localPosition), onPanUpdate: (details) { _controller.updatePaint(_getPosition( MediaQuery.of(context).size.width, details.localPosition)); }, onPanEnd: (_) => _controller.endPaint(), child: CustomPaint(painter: Signature(_state, context)), ), ), const SizedBox(height: 60), Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ ElevatedButton( onPressed: _controller.undo, style: ElevatedButton.styleFrom( shape: const CircleBorder(), primary: _controller.canUndo ? Theme.of(context).accentColor : Colors.grey[200], onPrimary: Colors.white, padding: const EdgeInsets.all(10), ), child: const Icon(Icons.undo, size: 40), ), ElevatedButton( onPressed: _controller.redo, style: ElevatedButton.styleFrom( shape: const CircleBorder(), primary: _controller.canRedo ? Theme.of(context).accentColor : Colors.grey[200], padding: const EdgeInsets.all(10), onPrimary: Colors.white, ), child: const Icon(Icons.redo, size: 40), ), ElevatedButton( onPressed: _controller.clear, style: ElevatedButton.styleFrom( shape: const CircleBorder(), primary: Colors.red, padding: const EdgeInsets.all(10), onPrimary: Colors.white, ), child: const Icon(Icons.delete, size: 40), ), ], ), const Spacer(), ], ), ); } // ポジションの取得 Offset _getPosition(double length, Offset localPosition) { double _dx; double _dy; if (localPosition.dx < 0) { _dx = 0; } else if (localPosition.dx > length) { _dx = length; } else { _dx = localPosition.dx; } if (localPosition.dy < 0) { _dy = 0; } else if (localPosition.dy > length) { _dy = length; } else { _dy = localPosition.dy; } return Offset(_dx, _dy); } } class Signature extends CustomPainter { Signature(this.state, this.context); final DrawState state; final BuildContext context; @override void paint(Canvas canvas, Size size) { const strokeWigth = 12.0; final paint = Paint() ..color = Colors.black ..strokeCap = StrokeCap.round ..strokeWidth = strokeWigth; for (final points in state.paintList) { canvas.drawRRect( RRect.fromRectAndRadius( Rect.fromCenter( center: points[0], width: strokeWigth, height: strokeWigth), const Radius.circular(strokeWigth), ), paint, ); for (var i = 0; i < points.length - 1; i++) { canvas.drawLine(points[i], points[i + 1], paint); } } } @override bool shouldRepaint(Signature oldDelegate) => oldDelegate.state.paintList != state.paintList; } dart_controller.dart import 'package:flutter/material.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:state_notifier/state_notifier.dart'; part 'draw_controller.freezed.dart'; // ステート @freezed abstract class DrawState with _$DrawState { const factory DrawState({ @Default(<List<Offset>>[]) List<List<Offset>> paintList, @Default(<List<Offset>>[]) List<List<Offset>> undoList, @Default(false) bool isDrag, }) = _DrawState; } // コントローラー final drawController = StateNotifierProvider.autoDispose<DrawController, DrawState>( (ref) => DrawController()); class DrawController extends StateNotifier<DrawState> { DrawController() : super(const DrawState()); bool get canUndo => state.paintList.isNotEmpty; bool get canRedo => state.undoList.isNotEmpty; void undo() { // 描画中か、undoできなかったら何もしない if (state.isDrag || !canUndo) { return; } // paintListの最後を取って、undoListに追加する final _last = state.paintList.last; state = state.copyWith( undoList: List.of(state.undoList)..add(_last), paintList: List.of(state.paintList)..removeLast(), ); } void redo() { // 描画中か、redoできなかったら何もしない if (state.isDrag || !canRedo) { return; } // undoListの最後を取って、paintListに追加する final _last = state.undoList.last; state = state.copyWith( undoList: List.of(state.undoList)..removeLast(), paintList: List.of(state.paintList)..add(_last), ); } void clear() { if (!state.isDrag) { state = state.copyWith(paintList: [], undoList: []); } } void addPaint(Offset startPoint) { if (!state.isDrag) { state = state.copyWith( isDrag: true, paintList: List.of(state.paintList)..add([startPoint]), undoList: const [], ); } } void updatePaint(Offset nextPoint) { if (state.isDrag) { final _paintList = List<List<Offset>>.of(state.paintList); final _offsetList = List<Offset>.of(state.paintList.last)..add(nextPoint); _paintList.last = _offsetList; state = state.copyWith(paintList: _paintList); } } void endPaint() => state = state.copyWith(isDrag: false); }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

FlutterがiOSシミュレータを認識しなかった話

前置き 経緯の説明から。 XCodeをApp Storeから入れていたのですが、容量が大きすぎるので一度削除しました。代わりにApple Developerサイトからコマンドラインツールをダウンロードして入れたのですが、今度はシミュレータを入れれず。 色々試したあと、入れてからシミュレータを厳選する作戦を思いつき、Apple DeveloperからXCodeをダウンロードしました。 症状 XCodeを入れているのに、iOSシミュレータを認識しませんでした。シミュレータを起動して Flutter devices と入力しても、デバイスが出てきません。そこで調べていると、以下を見つけました。 手順 XCodeを開く メニューバー > Xcode > Preferance > Location > Command Line Toolsが空白になっていたら、選択して埋める。 これで解決しました。お役に立てましたら幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】Int型 ⇄ 16進数文字列 ⇄ バイト配列(UInt8配列) を相互変換する

はじめに BLE通信などを実装していると、Write / Read のタイミングでバイト型を扱うと思います。 業務でファームウェアの仕様に合わせ、Int型 ⇄ 16進数文字列 ⇄ バイト配列 で相互変換する必要がありました。 ここら辺は忘れやすいかなと思い、まとめることにしました! 環境 Xcode12.5 Swift5 変換していく 今回は、以下のデータを変換していきます。 Int型 16進数文字列 バイト配列(UInt8配列) 123456789 "075BCD15" [7, 91, 205, 21] 16進数文字列 -> Byte配列 ごりごり加工していくので、別途 extension に関数を定義しています。 extension String { func toBytes() -> [UInt8]? { var hexString = self if hexString.count % 2 == 1 { hexString = "0" + hexString } var bytes = [UInt8]() bytes.reserveCapacity(hexString.count / 2) var index = hexString.startIndex for _ in 0..<hexString.count / 2 { let nextIndex = hexString.index(index, offsetBy: 2) if let byte = UInt8(hexString[index..<nextIndex], radix: 16) { bytes.append(byte) } else { return nil } index = nextIndex } return bytes } } 上の関数を使ってみます。 let hexString = "075BCD15" let bytes = hexString.toBytes() print(bytes) // Optional([7, 91, 205, 21]) Byte配列 -> 16進数文字列 0埋めできていないですね。。。 let bytes = Data([7, 91, 205, 21]) let hexString = bytes.map { String(format: "%02hhX", $0) }.joined() print(hexString) // 75BCD15 Int型 -> 16進数文字列 let integer = 123456789 let hexString = String(format: "%02X", integer) print(hexString) // 1234567879 16進数文字列 -> Int型 let hexString = "075BCD15" let integer = Int(hexString, radix: 16) print(integer) // Optional(123456789) 以上です! 参考にした記事 How to convert an Int to Hex String in Swift How to convert Data to hex string in swift Swift 3 で16進数文字列と整数の相互変換 How do i convert HexString To ByteArray in Swift 3
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】[備忘] Int型 ⇄ 16進数文字列 ⇄ バイト配列(UInt8配列) の相互変換

はじめに BLE通信などを実装していると、Write / Read のタイミングでバイト型を扱うと思います。 業務でファームウェアの仕様に合わせ、Int型 ⇄ 16進数文字列 ⇄ バイト配列 で相互変換する必要がありました。 ここら辺は忘れやすいかなと思い、まとめることにしました! 環境 Xcode12.5 Swift5 変換していく 今回は、以下のデータを変換していきます。 Int型 16進数文字列 バイト配列(UInt8配列) 123456789 "075BCD15" [7, 91, 205, 21] 16進数文字列 -> Byte配列 ごりごり加工していくので、別途 extension に関数を定義しています。 extension String { func toBytes() -> [UInt8]? { var hexString = self if hexString.count % 2 == 1 { hexString = "0" + hexString } var bytes = [UInt8]() bytes.reserveCapacity(hexString.count / 2) var index = hexString.startIndex for _ in 0..<hexString.count / 2 { let nextIndex = hexString.index(index, offsetBy: 2) if let byte = UInt8(hexString[index..<nextIndex], radix: 16) { bytes.append(byte) } else { return nil } index = nextIndex } return bytes } } 上の関数を使ってみます。 let hexString = "075BCD15" let bytes = hexString.toBytes() print(bytes) // Optional([7, 91, 205, 21]) Byte配列 -> 16進数文字列 0埋めできていないですね。。。 let bytes = Data([7, 91, 205, 21]) let hexString = bytes.map { String(format: "%02hhX", $0) }.joined() print(hexString) // 75BCD15 Int型 -> 16進数文字列 let integer = 123456789 let hexString = String(format: "%02X", integer) print(hexString) // 1234567879 16進数文字列 -> Int型 let hexString = "075BCD15" let integer = Int(hexString, radix: 16) print(integer) // Optional(123456789) 以上です! 参考にした記事 How to convert an Int to Hex String in Swift How to convert Data to hex string in swift Swift 3 で16進数文字列と整数の相互変換 How do i convert HexString To ByteArray in Swift 3
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む