- 投稿日:2020-11-23T23:52:14+09:00
【SwiftUI】Likeボタンとリスト内セルにボタンを実装する場合の注意点
はじめに
SwiftUIでFirebaseからデータを読み取り、これまで作成したカード内容を更新することを目的とする。
今回はカード内のLikeボタンの実装を行います。
結論を最初に言うと、ListView内でButtonを実装する場合はそれぞれのImageに
.onTapGesture
を設定する必要があります。
その点がわかっていなかったため、実装に時間がかかってしまいました。前回までの記事は以下を参考ください。
参考記事
【SwiftUI】Firebaseからデータを読み取り、ListViewのRowを更新する開発環境
OSX 10.15.7 (Catalina)
Xcode 12.2.0
CocoaPods 1.10.0Like機能の実装
ボタンの設定に時間がかかってしまったため、FirebaseのLike数の更新機能の実装までは至りませんでした。
今回はタップした際にハートの色を変更する機能のみ実装します。
設定のみしたaction機能は次回以降に実装します。実装方法
struct ContentView: View { @State private var isLiked = false var body: some View { VStack { Text(isLiked ? "Liked!" : "unLiked!") HeartButton(isLiked: $isLiked) } } } struct HeartButton: View { @Binding var isLiked : Bool var body: some View { Button(action: { self.isLiked.toggle() }, label: { Image(systemName: isLiked ? "heart.fill": "heart") .font(.largeTitle) .foregroundColor(Color.red) .frame(width: 100, height: 100) }) } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }コード全体
今回機能の実装にあたり、View内のコードを見直し、修正しました。
上記のコードを参考に、List内のカードに実装します。
List内にButtonを実装する場合は、実装した場合はセル全体が選択可能になってしまい、特定のボタンのみを選択することができません。
そのため、ボタンの代わりにtextやimageに.onTapGesture
を実装する必要があります。
現在は色が変わるだけしか実装できていません。
今後はタップした場合にFirebaseを更新し、Like数が増減できるようにする予定です。struct ContentRowView: View { var id = "" var user = "" var userImage = "" var haiku = "" var mapImage = "" var place = "" var likes = "" // ○秒、○日、○年前を表示 var createdDate = "" static let formatter = RelativeDateTimeFormatter() // 以下を追記 @State var isLiked = false var body: some View { ContentRowView.formatter.locale = Locale(identifier: "ja_JP") let fmt = ISO8601DateFormatter() let date1 = fmt.date(from: createdDate)! let components = Calendar.current.dateComponents( [.day, .year, .month, .minute, .second], from: Date(), to: date1 ) let timeAgo = ContentRowView.formatter.localizedString(from: components) return VStack { // mapImage VStack { AnimatedImage(url: URL(string: mapImage)!) .resizable() //.aspectRatio(contentMode: .fit) .cornerRadius(12.0, antialiased: /*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/) HStack { Spacer() Text(place).font(.caption).foregroundColor(.gray) .padding(.bottom, 5) } } // Haiku Text(haiku).font(.title3).fontWeight(.bold) // Likes Number HStack { Image(systemName: "heart").font(.headline).foregroundColor(Color("pinkColor")) Text(likes).font(.headline).foregroundColor(Color("pinkColor")) Spacer() }.padding(.top, 5) // Info HStack { AnimatedImage(url: URL(string: userImage)!) .resizable() .frame(width: 30, height: 30) .clipShape(Circle()) Text(user).font(.headline).fontWeight(.light) + Text("・").font(.headline).fontWeight(.light) + Text(timeAgo).font(.headline).fontWeight(.light) Spacer() // If Comment Button was push, it call CommentView. Image(systemName: "text.bubble") .font(.title) .foregroundColor(.gray) .onTapGesture { print("Button Tapped") } .frame(width: 30, height: 30) // Like Button push Image to add like's Number. Image(systemName: isLiked ? "heart.fill": "heart") .font(.title) .foregroundColor(Color("pinkColor")) .onTapGesture { self.isLiked.toggle() } .frame(width: 30, height: 30) }.padding(5) }.padding(.top, 8).frame(height: 391) } }追記
エラー
下記のエラーを確認して、修正しようとしましたが、現状では解決できないエラーのようです。
無視しても致命的にはならない現象のため、いったんそのままにします。nw_protocol_get_quic_image_block_invoke dlopen libquic failed
参考文献
Strange error nw_protocol_get_quic_image_block_invoke dlopen libquic failed
Swift Firebase Connection
- 投稿日:2020-11-23T23:03:51+09:00
Flutterチュートリアルを咀嚼する part2 -routeとnavigator-
part1ではFlutterの概要を掴んだ
part2ではpart1で作ったstartup_namerアプリにスマホアプリによくある機能を搭載する
・アイコンの利用
・いいね機能
・別ページへの遷移対象のチュートリアル
https://codelabs.developers.google.com/codelabs/first-flutter-app-pt2/#0
コピペしてできあがったもの
右上の(名前なんて言うのこれ)を押すと右にスライドしてもう一つのページが表示されるようになった
(いいねしたものだけが表示される)
ソースコード
main.dartimport 'package:flutter/material.dart'; import 'package:english_words/english_words.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Startup Name Generator', theme: ThemeData( primaryColor: Colors.white, ), home: RandomWords(), ); } } class RandomWords extends StatefulWidget { @override _RandomWordsState createState() => _RandomWordsState(); } class _RandomWordsState extends State<RandomWords> { final _suggestions = <WordPair>[]; final _saved = Set<WordPair>(); final _biggerFont = TextStyle(fontSize: 18.0); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Startup Name Generator'), actions: [ IconButton(icon: Icon(Icons.list), onPressed: _pushSaved), ], ), body: _buildSuggestions(), ); } Widget _buildSuggestions() { return ListView.builder( padding: EdgeInsets.all(16.0), itemBuilder: /*1*/ (context, i) { if (i.isOdd) return Divider(); /*2*/ final index = i ~/ 2; /*3*/ if (index >= _suggestions.length) { _suggestions.addAll(generateWordPairs().take(10)); /*4*/ } return _buildRow(_suggestions[index]); }); } Widget _buildRow(WordPair pair) { final alreadySaved = _saved.contains(pair); return ListTile( title: Text( pair.asPascalCase, style: _biggerFont, ), trailing: Icon( alreadySaved ? Icons.favorite : Icons.favorite_border, color: alreadySaved ? Colors.red : null, ), onTap: () { setState(() { if (alreadySaved) { _saved.remove(pair); } else { _saved.add(pair); } }); }, ); } void _pushSaved() { Navigator.of(context).push( MaterialPageRoute<void>( builder: (BuildContext context) { final tiles = _saved.map( (WordPair pair) { return ListTile( title: Text( pair.asPascalCase, style: _biggerFont, ), ); }, ); final divided = ListTile.divideTiles( context: context, tiles: tiles, ).toList(); return Scaffold( appBar: AppBar( title: Text('Saved Suggestions'), ), body: ListView(children: divided), ); }, ), ); } }順々にみていく
ハート形アイコンの追加
ハートアイコンと必要な変数の設定
class _RandomWordsState extends State<RandomWords> { final _suggestions = <WordPair>[]; final _saved = Set<WordPair>(); // NEW final _biggerFont = TextStyle(fontSize: 18.0); ... }Widget _buildRow(WordPair pair) { final alreadySaved = _saved.contains(pair); //NEW return ListTile( title: Text( pair.asPascalCase, style: _biggerFont, ), trailing: Icon( // NEW from here... alreadySaved ? Icons.favorite : Icons.favorite_border, color: alreadySaved ? Colors.red : null, ), // ... to here. ); }Set
https://api.flutter.dev/flutter/dart-core/Set-class.html
_savedはいいねをした文字をメモリ上に保管するための変数contains()
https://api.flutter.dev/flutter/package-collection_collection/PriorityQueue/contains.html
対象が_savedに格納されていたらtrue、そうでない場合はfalseを返す
true/falseはalreadySavedに格納されるListTile.trailing
https://api.flutter.dev/flutter/material/ListTile/trailing.html
ListTile(リストの1行分のデータ)のタイトルの後に設定するIcon用のプロパティIcon
https://api.flutter.dev/flutter/widgets/Icon-class.html
Icons.favorite
https://api.flutter.dev/flutter/material/Icons/favorite-constant.html
色付きのハートアイコン
色はcolor:~のところで赤色としている
alreadySavedがtrueならこのアイコンを赤色に設定しているIcons.favorite_border
https://api.flutter.dev/flutter/material/Icons/favorite_border-constant.html
色なし枠だけのハートアイコン
alreadySavedがfalseならこちらを設定しているColors
https://api.flutter.dev/flutter/material/Colors-class.html
色を司るクラスColors.red
https://api.flutter.dev/flutter/material/Colors/red-constant.html
赤色を引き出せるいいね
タップしたらその単語が(メモリ上に)保存され、ハートアイコンが赤色になる
Widget _buildRow(WordPair pair) { final alreadySaved = _saved.contains(pair); return ListTile( title: Text( pair.asPascalCase, style: _biggerFont, ), trailing: Icon( alreadySaved ? Icons.favorite : Icons.favorite_border, color: alreadySaved ? Colors.red : null, ), onTap: () { // NEW lines from here... setState(() { if (alreadySaved) { _saved.remove(pair); } else { _saved.add(pair); } }); }, // ... to here. ); }onTap
https://api.flutter.dev/flutter/cupertino/CupertinoTabBar/onTap.html
アイテムがタップされたときに呼び出されるコールバック。setState()
https://api.flutter.dev/flutter/material/ScaffoldFeatureController/setState.html
Flutterにstate(状態)が変わったことを知らせる
これによりStatefulWidgetの状態が変化する
今回はalreadySavedがtrueなら、_savedから対象を取り除き(いいねをやめる)、falseなら追加する右ページへの移動
class _RandomWordsState extends State<RandomWords> { ... @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Startup Name Generator'), actions: [ IconButton(icon: Icon(Icons.list), onPressed: _pushSaved), ], ), body: _buildSuggestions(), ); } ... }IconButton
https://api.flutter.dev/flutter/material/IconButton-class.html
押したら(onPressed)反応するアイコンボタン
今回は_pushSavedを呼び出しているIcons.list
https://api.flutter.dev/flutter/material/Icons/list-constant.html
リスト形のアイコン(画面右上にでているの)void _pushSaved() { Navigator.of(context).push( MaterialPageRoute<void>( builder: (BuildContext context) { final tiles = _saved.map( (WordPair pair) { return ListTile( title: Text( pair.asPascalCase, style: _biggerFont, ), ); }, ); final divided = ListTile.divideTiles( context: context, tiles: tiles, ).toList(); return Scaffold( appBar: AppBar( title: Text('Saved Suggestions'), ), body: ListView(children: divided), ); }, ), ); } }Navigator
https://api.flutter.dev/flutter/dart-html/Navigator-class.html
Flutterでは新しいページのことをrouteと呼ぶ。
根っこ(root)ではなく経路とかの意味のルートである。
Navigatorは別のrouteへユーザを導く。
※使い方Navigator.of(context).push( );MaterialPageRoute
https://api.flutter.dev/flutter/material/MaterialPageRoute-class.html
これが右に移動したら現れる新しいページの本体
中身はいいねした単語がリスト表示されている全体のテーマを変える
class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Startup Name Generator', theme: ThemeData( // Add the 3 lines from here... primaryColor: Colors.white, ), // ... to here. home: RandomWords(), ); } }ThemeData
https://api.flutter.dev/flutter/material/ThemeData-class.html
MaterialAppのthemeを変更することでアプリ全体のテーマを変更できる
今回は白くしたこれでWidgetの使い方
アイコン、新しいページの生成方法が学べた
基礎を理解できる良いチュートリアルだと思う
- 投稿日:2020-11-23T22:56:05+09:00
[SwiftUI] Viewを強制再読み込みする賢くない方法
どうしてもViewを再読み込みしたいが、どうやってもうまくいかない、という場合、賢くないですがこの方法でいけます。
SwiftUIのお気持ちに沿って作っていればそもそも強制再読み込みは必要ないはずなので、対処法として正しいのは設計の見直しです。対処
struct HogeView: View { var body: some View { Hoge() } }を強制再読み込みしたい場合、
struct HogeView: View { @State private var flag = true func refresh(){ flag.toggle() } var mainView: some View { Hoge() } var body: some View { Group{ if flag{ mainView }else{ mainView } } } }としてあげれば、flagを切り替えるたびにmainViewが計算しなおされるので実質再読み込みになります。
自戒
本当に賢くないのでSwiftUIをちゃんと勉強します。
- 投稿日:2020-11-23T22:10:36+09:00
SwiftUIだけど画面遷移はUIKitでやる
画面遷移処理を各画面から切り離したり、カスタムURLスキームなどを使って任意の画面に遷移できるようにする処理をSwiftUIでもやりたい。でもSwiftUIだけで実現する方法がわからない。。。
難しそうなところは今後のSwiftUIの進化に期待するということで、画面遷移は無理せずUIKitベースでやってしまえば良さそうだなと思い始めました。環境
- Xcode 12.0
実装
各画面のレイアウトはSwiftUIでさくっと作ってしまって画面遷移に関連する部分はUIKitベースで処理するために、遷移先はUIHostingControllerを使う。ViewControllerを見つける処理は従来通り。
var window: UIWindow? { guard let window = (UIApplication.shared.connectedScenes.first?.delegate as? UIWindowSceneDelegate)?.window else { return nil } return window } /// 全面に表示されているViewControllerを見つける func topViewController(_ vc: UIViewController? = nil) -> UIViewController? { guard let vc = vc ?? window?.rootViewController else { return nil } if let presented = vc.presentedViewController { return topViewController(presented) } return vc } /// NavigationControllerを見つける func navigationController(_ vc: UIViewController) -> UINavigationController? { if let result = vc as? UINavigationController { return result } for child in vc.children { if let result = navigationController(child) { return result } } return nil } @main struct MainApp: App { var body: some Scene { WindowGroup { ContentView() .onOpenURL(perform: { _ in guard let topVC = topViewController() else { return } if let navVC = navigationController(topVC) { navVC.show(UIHostingController(rootView: TestView()), sender: nil) } else { topVC.present(UIHostingController(rootView: NavigationView(content: { TestView() })), animated: true, completion: nil) } }) } } }SwiftUIで作った画面をXcodeのDebug View Hierarchyで見てみると、ViewControllerらしきコンポーネントがたくさん使われているようだったので、上記の実装は「NavigationViewを使えばUINavigationControllerが内部的には使われているかもしれない」とか、「sheetでモーダル表示したらpresentedViewControllerで遷移先のViewControllerを見つけられるかもしれない」という思い込みで実装してみました。
SwiftUIのNavigationViewを使っている場合にUINavigationControllerを探索可能かどうか不明でしたが、UINavigationControllerを継承していそうなクラスが使われているようでした。
- 投稿日:2020-11-23T18:36:17+09:00
AVPlayerViewControllerのコントロールバーを監視する
概要
画面をタップすると表示・非表示が切り替わるコントロールバーを監視します。
あまりニーズがなさそうな情報ですが、つい最近コントロールバーの表示に合わせて自作UIを表示するという要件が実際にありましたのでメモを兼ねて投稿します。開発環境
Xcode 12.1
Swift 5AVPlayerViewControllerのレイヤー構成
AVPlayerViewControllerの動画再生時の画面レイヤーはこのようになっています。
目的のバーはAVViewの配下にあります。監視対象
バーの表示・非表示の切り替えはAVViewのisHiddenプロパティではなく親のAVTouchIgnoringViewのisHiddenプロパティで行われているのでこいつを監視します。
※Objectice-Cの場合は"isHidden"ではなく"hidden"キーになるようです実装
ViewController.swift@IBAction func pressedMoviePlayButton() { let playerViewController = CustomAVPlayerViewController() self.present(playerViewController, animated: true) { playerViewController.player?.play() playerViewController.find(view: playerViewController.view) } }CustomAVPlayerViewControllerprivate var observers = [NSKeyValueObservation]() override func viewDidLoad() { super.viewDidLoad() let path = Bundle.main.path(forResource: "sample", ofType: "mp4")! let url: URL = .init(fileURLWithPath: path) let item: AVPlayerItem = .init(url: url) let player: AVPlayer = .init(playerItem: item) self.player = player } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) // 監視を解除する observers.forEach { $0.invalidate() } observers.removeAll() } // AVTouchIgnoringViewを探す func find(view: UIView) { let targetViewName = "AVTouchIgnoringView" view.subviews.forEach { if !self.observers.isEmpty { return } // isHiddenプロパティをobserveする if String(describing: type(of: $0)).isEqual(targetViewName) { self.observers.append($0.observe(\.isHidden, options: .new, changeHandler: { (_, change) in print("\(change.newValue)") })) return } self.find(view: $0) } }
- 投稿日:2020-11-23T17:35:06+09:00
SwiftUIの多言語化
概要
LocalizedStringKey
を使ってSwiftUIの多言語化を行います。3行まとめ
Text
に文字列定数を渡すと、文字列定数をキーにしてローカライズが行われます。XLIFFのエクスポートもサポートされます。LocalizedStringKey
を使うと、引数をキーにして各種コンポーネントのローカライズ対応ができます。が、XLIFFのエクスポートはサポートされません。
NSLocalizedString
をコメントの形としてつけておくとXLIFFのエクスポートに対応することが可能です。Textの多言語化
Textのパラメータに、ダブルクォーテーションで囲われた文字列定数を設定します。
Export for localization
を行うと、Textの文字列を定数にしたものが全てXLIFFに出力されます。これをもって翻訳に使ったり、SwiftUIのプレビューから各言語の表示を確認できたりできます。コード
SwiftUI
import SwiftUI struct ContentView: View { var body: some View { Text("Hello, world!") } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() .environment(\.locale, Locale(identifier: "ja")) } }XLIFF
<?xml version="1.0" encoding="UTF-8"?> <xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 http://docs.oasis-open.org/xliff/v1.2/os/xliff-core-1.2-strict.xsd"> <file original="l18/en.lproj/InfoPlist.strings" datatype="plaintext" source-language="en" target-language="ja"> <header> <tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="12.2" build-num="12B45b"/> </header> <body> <trans-unit id="CFBundleName" xml:space="preserve"> <source>l18</source> <note>Bundle name</note> </trans-unit> </body> </file> <file original="l18/en.lproj/Localizable.strings" datatype="plaintext" source-language="en" target-language="ja"> <header> <tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="12.2" build-num="12B45b"/> </header> <body> <trans-unit id="Hello, world!" xml:space="preserve"> <source>Hello, world!</source> <note>No comment provided by engineer.</note> </trans-unit> </body> </file> </xliff>Textに定数を設定する場合以外
例えば
Label
を使う場合などは、文字列の定数を設定したとしてもその定数をソースとしたXLIFFは出力されません。この場合、従来のコードで使っていたNSLocalizedString
を使って対応することになると思います。Labelのコード
SwiftUI
import SwiftUI struct ContentView: View { let text = NSLocalizedString("Hello, world!", comment: "Hello, world!") var body: some View { Label(text, systemImage: "arrow.uturn.up") } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() .environment(\.locale, Locale(identifier: "ja")) } }SwiftUIで言語のプレビューが効かない問題と解消法
しかし、NSLocalizedStringで対応を行なった場合、SwiftUIのプレビューでlocaleのenvironmentを切り替えたとしても言語の確認ができないという問題が発生します。
そこでLocalizedStringKey
を使います。LocalizedStringKeyにキーを指定すると、SwiftUIのプレビューで確認ができるようになります。
ただし、LocalizedStringKeyもXLIFFのエクスポートに対応していないので、コメントの形としてNSLocalizedStringを書く形にするとXLIFFへのエクスポートにも対応できます。SwiftUI
import SwiftUI struct ContentView: View { // NSLocalizedString("Hello, world!", comment: "Hello, world!") let text = LocalizedStringKey("Hello, world!") var body: some View { Label(text, systemImage: "arrow.uturn.up") } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() .environment(\.locale, Locale(identifier: "ja")) } }追記
LocalizedStringKeyのキーは、ローカライズされていない時にデフォルトで表示されるテキストになるので、多くの場合は表示される英語のテキストをキー名にしたほうがいいんじゃないかと思います。
OK
LocalizedStringKey("Hello, world!")NG
LocalizedStringKey("hello_world") LocalizedStringKey("helloWorld")
- 投稿日:2020-11-23T17:06:17+09:00
XcodeでiMessage Extensionを削除する方法
XcodeでiMessage Extensionを追加するときは、「File」 > 「New」 > 「Target...」から簡単に行えますが、、
削除の手順が分からなかったため、Appleのテクニカルサポート(DTS)へ問い合わせを行なった記録です。削除手順
以下の3手順にしたがって削除していきます。
1. Xcodeでターゲットを削除する
ターゲットを削除するには、プロジェクトツリーからプロジェクトを選択します。
※ すみません、背面にいたQiitaの緑色が写り込んでしまいました、、 ?そのあと、ターゲット(iMessage Extension)を選択し、右クリックで削除するのですが、、
「General」の左側にいるアイコンを押すと、こちらのサイドバーが現れます。(はじめ、これに気づくのに苦労しました...)「Delete」でターゲットは無事に削除されます。
2. 「Manage Schemes...」をクリックしてスキームを削除する
スキームの削除は、トップメニューから行えます。
「Product」 > 「Scheme」 > 「Manage Schemes...」 から管理画面を表示します。リストの中から該当するもの(今回は iMessage Extension)を選択し、左下のマイナスボタンをクリックします。
「Delete」でスキームが無事に削除されます。
3. 関連するソースコードを削除する
最後に関連するソースコードをプロジェクトツリーから右クリックで削除します。
私の場合は、これだけでした。おわりに
Appleへの問い合わせは、2018年9月時点のものです。 ※ キャプチャは Xcode 11.1
操作方法などに変更がある場合もありますので、ご注意ください。
- 投稿日:2020-11-23T17:05:03+09:00
[iOS]FDL(Firebase Dynamic Links)とは
Firebase Dynamic Linksとは
ユーザーがあるURLを踏んだ場合、
- iOSデバイスを使っている時、
- あるアプリがインストールされている時は、そのアプリでコンテンツを開く
- インストールされていない時は、そのアプリのストアのページを開く
- Androidデバイスを使っている時、
- あるアプリがインストールされている時は、そのアプリでコンテンツを開く
- インストールされていない時は、そのアプリのストアのページを開く
- そのほかのデバイスを使っている時は、Webブラウザでコンテンツを開く
という風に、デバイスやアプリのインストール状況に応じて違う振る舞いをしたい時がある。
ただ、しかし、それぞれの振る舞いごとに違うURLを用意していては、何個もURLが必要になりユーザーがどれをタップしていいかわからないし、そもそもURLを何個も書ききれないだろう(例えば、ツイッターの一つの投稿にURLを5つも載せるのは無理である)。
一つのURLだけで、状況に応じて違う振る舞いができるのが、Firebase Dynamic Linksである。
下記ページのように、FDLの動きのフローチャートを見ると分かりやすい。
Dynamic Links をデバッグする導入手順
Firebase Dynamic Linksの生成
Firebase Dynamic Linksをアプリ側で受け取る
なおリンク先に記載があるが、iOS9以降ではカスタムURLスキームと Universal Linksが両方使われる(アプリインストール直後のFDLのみ、カスタムURLスキームとして受け取る)事に注意。
- 投稿日:2020-11-23T15:23:41+09:00
Auto LayoutのStack Viewの便利さ
はじめに
Auto Layoutを勉強していてStack Viewの便利さを知ったので記事を書こうと思います。
また、Auto Layoutはエンジニア必須スキルで、ちゃんと理解できれば他のエンジニアと差をつけられる要素になるみたいです。
しっかり使いこなせるようになりたいですね。Stack Viewとは何か
公式ドキュメントにはこのように書かれています。
Stack Viewは、複雑な制約を導入することなく、自動レイアウトの機能を活用する簡単な方法を提供します。
要するに、本来なら複雑な制約を書いて実装するけど、Stack Viewを使えば簡単に実装できるようになります!って感じです。
使ってみた
今回はボタンを並べて、後からボタンを追加する作業にStack Viewを使っていきます。
Stack ViewをViewに追加
HorizontalとVerticalがあります。
Horizontalは水平なので横並び、Verticalは垂直なので縦並びで設定したい時に使います。
今回はHorizontalを使います。
Stack Viewにボタンを上から載せて追加
配置を設定
topとcenterの制約を設定します。
右下にあるAdd New Constraintsでtopの制約を追加します。
次に、Stack Viewをコントロール押しながらドラックしてViewで離します。
すると画像のような選択肢が出てくるので、Center Horizontallyを選択肢します。
これで配置が完了しました。
それぞれのボタンのスペースを8に設定してみました。
ボタンを追加する
さて、ここでボタン追加の変更が入ったとします。Stack Viewでなければボタンを追加して、配置なども1からやり直しでしたが。
Stack Viewを使っているので、Stack Viewのなかにボタンをドロップするだけで配置することができます。
もちろん、さっき設定したスペースの8も自動で設定されますので、設定し直す必要がありません。
最後に
レイアウトを管理することができるのがStack Viewの役割です。
これはかなり便利な機能だなと思いました。
まだこのくらいしか使い方知りませんが、また便利な使い方があれば更新していきます。参考サイト
- 投稿日:2020-11-23T09:57:13+09:00
WindowsとMacのショートカットキー対応表
執筆の動機
今後、学んだことをQiitaでアウトプットしていきたい。
しかし記事を作成した経験が無く、まずは投稿してみたかった。
そんな中、人生で初めてMacを購入しました。
Ctrlキーが無かったり、入力方式(かな、半角英数)を切り替える方法が分からなかったりと操作感が異なり興奮の連続です。新しいことを学ぶのって楽しいですよね。
Qiita初投稿には良い題材ではないかと考え、私がWindowsでよく使用するショートカットキーをMacではどのように使用するのか調べてまとめることにしました。ショートカットキー対応表
操作 Windows Mac テキストをコピー Ctrl + C command + C コピーしたテキストを貼り付け Ctrl + V command + V テキストを切り取り Ctrl + X command + X 操作を戻す Ctrl + Z command + Z 操作を戻すを取り消し Ctrl + Y command + Shift + Z デスクトップ画面を表示 Windwos + D fn + F11 アクティブウィンドウの切り替え Alt + tab command + tab 画面をロック Windows + L command + control + Q ウィンドウを最大化 Windows + ↑ command + control + F 入力方式切り替え 半角/全角 control + スペース 普段どのようなショートカットキーを使用しているかなかなか思い出せませんね。
記事作成の良い練習になりました。
- 投稿日:2020-11-23T08:15:47+09:00
U-2Netをつかう。
画像内の顕著なオブジェクトをセグメントしてくれるモデル。
手順
1、U2-NetのGitHubリポジトリをクローン
git clone https://github.com/NathanUA/U-2-Net.git cd U-2-Net/
2、事前学習済みモデルをリポジトリのリンクからダウンロードし、指定のディレクトリに配置
mkdir saved_models/u2netp/
3、とりあえずPythonで試してみる。
リポジトリで配布されているモデルは174MBと4MBの2種類あって、軽量の方をつかう場合は、model_nameをu2netpに書き換えます。
あと、PytorchでデフォルトはCudaデバイス(GPU)をつかう設定になっているので、CPUで実行する場合はtorch.deviceを切り替えます。
u2net_test.pymodel_name='u2net'#u2netppython u2net_test.py?
お仕事のご相談こちらまで
rockyshikoku@gmail.comCore MLを使ったアプリを作っています。
機械学習関連の情報を発信しています。
- 投稿日:2020-11-23T08:14:33+09:00
PytorchモデルをONNX経由でCoreMLモデルに変換する(あえてね)
CoreMLTools4.0からPytorchモデルを直接(Traced_Model経由で)変換できるようになりました。
旧式のONNX経由で変換するやりかたは非推奨になったのだけど、とはいえ「直接変換はできない」かつ「旧式ONNX方式だと変換できた」ケースもあったので、書いておくです?1.PyTorchモデルをONNXにエクスポート
x = torch.randn(1, 3, 224, 224, requires_grad=True).cuda() #ダミーインプット(batch_size,color_channel,width,height) モデルがCuda使ってるなら、.cuda() torch.onnx.export(torch_model, # モデル x, # ダミーインプット "torch_model.onnx", # 保存パス export_params=True, # モデルの重みも保存するか # opset_version=10, # エクスポートするONNXのバージョン # do_constant_folding=True, # 最適化のために定数畳み込みを実行するかどうか # input_names = ['input'], # the model's input names # output_names = ['output'], # the model's output names dynamic_axes={'input' : {0 : 'batch_size'}, 'output' : {0 : 'batch_size'}})2.CoreMLTools ONNX_CoreMLをインストール
pip install coremltools==3.4 pip install onnx_coreml3.ONNXをCoreMLに変換
from onnx_coreml import convert mlmodel = convert("torch_model.onnx") mlmodel.save("torch_model.mlmodel")?
お仕事のご相談こちらまで
rockyshikoku@gmail.comCore MLを使ったアプリを作っています。
機械学習関連の情報を発信しています。