20211010のSwiftに関する記事は5件です。

UIMenuを使ってメニュー画面を表示

今回の内容 UIMenu()でボタンを押したときに、メニューを表示出来る様にする。 コードと簡単解説 .displayInline displayInlineMenuButton.menuにUIMenu()を設定することで、UIMenu(children: [UIMenuElement])に設定しているUIMenu()を表示する値として追加します。 UIMenu(options: .displayInline)は表示するUIAction()をUIButton()を押した時に全て表示します。 UIMenu()を表示するには.showsMenuAsPrimaryActionをtrueで設定します。 override func viewDidLoad() { super.viewDidLoad() let displayInlineMenuButton = {() -> UIButton in let button = UIButton(frame: CGRect(x: view.frame.maxX / 4, y: view.frame.maxY / 10, width: view.frame.width / 2, height: view.frame.height / 20)) button.setTitle("displayInlineMenu", for: .normal) button.titleLabel?.textColor = .black button.backgroundColor = .systemGreen button.showsMenuAsPrimaryAction = true return button }() view.addSubview(displayInlineMenuButton) displayInlineMenuButton.menu = UIMenu(title:"displayInlineMenu",children: [ UIMenu(title: "", options: .displayInline, children: [ UIAction(title:"displayInlineMenu01",handler: { _ in print("displayInlineMenu01が押されました") }), UIAction(title:"displayInlineMenu02",handler: { _ in print("displayInlineMenu02が押されました") }), UIAction(title:"displayInlineMenu03",handler: { _ in print("displayInlineMenu03が押されました") }), UIAction(title:"displayInlineMenu04",handler: { _ in print("displayInlineMenu04が押されました") }), UIAction(title:"displayInlineMenu05",handler: { _ in print("displayInlineMenu05が押されました") }) ]) ]) } .destructive destructiveMenuButton.menuにUIMenu()を設定することで、UIMenu(children: [UIMenuElement])に設定しているUIMenu()を表示する値として追加します。 UIMenu(title: "destructiveMenu", options: .destructive)でUIMenu()を設定した時、UIAction()を全て表示する為のメニューが追加されます。追加されたメニューを押すと全てのUIAction()が表示されます。 UIMenu()を表示するには.showsMenuAsPrimaryActionをtrueで設定します。 override func viewDidLoad() { super.viewDidLoad() let destructiveMenuButton = {() -> UIButton in let button = UIButton(frame: CGRect(x: view.frame.maxX / 4, y: view.frame.maxY / 5, width: view.frame.width / 2, height: view.frame.height / 20)) button.setTitle("destructiveMenu", for: .normal) button.titleLabel?.textColor = .black button.backgroundColor = .systemBlue button.showsMenuAsPrimaryAction = true return button }() view.addSubview(destructiveMenuButton) destructiveMenuButton.menu = UIMenu(title:"destructiveMenu",children: [ UIMenu(title: "destructiveMenu", options: .destructive, children: [ UIAction(title:"destructiveMenu01",handler: { _ in print("destructiveMenu01が押されました") }), UIAction(title:"destructiveMenu02",handler: { _ in print("destructiveMenu02が押されました") }), UIAction(title:"destructiveMenu03",handler: { _ in print("destructiveMenu03が押されました") }), UIAction(title:"destructiveMenu04",handler: { _ in print("destructiveMenu04が押されました") }), UIAction(title:"destructiveMenu05",handler: { _ in print("destructiveMenu05が押されました") }) ]) ]) } .displayInlineと.destructiveを同時に表示 override func viewDidLoad() { super.viewDidLoad() let displayInlineAnddestructiveMenuButton = {() -> UIButton in let button = UIButton(frame: CGRect(x: view.frame.maxX / 4, y: view.frame.maxY / 3.3, width: view.frame.width / 2, height: view.frame.height / 20)) button.setTitle("displayInlineAnddestructiveMenu", for: .normal) button.titleLabel?.adjustsFontSizeToFitWidth = true button.titleLabel?.textColor = .black button.backgroundColor = .systemRed button.showsMenuAsPrimaryAction = true return button }() view.addSubview(displayInlineAnddestructiveMenuButton) displayInlineAnddestructiveMenuButton.menu = UIMenu(title:"displayInlineMenu",children: [ UIMenu(title: "", options: .displayInline, children: [ UIAction(title:"displayInlineMenu01",handler: { _ in print("displayInlineMenu01が押されました") }), UIAction(title:"displayInlineMenu02",handler: { _ in print("displayInlineMenu02が押されました") }), UIAction(title:"displayInlineMenu03",handler: { _ in print("displayInlineMenu03が押されました") }), UIAction(title:"displayInlineMenu04",handler: { _ in print("displayInlineMenu04が押されました") }), UIAction(title:"displayInlineMenu05",handler: { _ in print("displayInlineMenu05が押されました") }) ]), UIMenu(title: "destructiveMenu", options: .destructive, children: [ UIAction(title:"destructiveMenu01",handler: { _ in print("destructiveMenu01が押されました") }), UIAction(title:"destructiveMenu02",handler: { _ in print("destructiveMenu02が押されました") }), UIAction(title:"destructiveMenu03",handler: { _ in print("destructiveMenu03が押されました") }), UIAction(title:"destructiveMenu04",handler: { _ in print("destructiveMenu04が押されました") }), UIAction(title:"destructiveMenu05",handler: { _ in print("destructiveMenu05が押されました") }) ]) ]) } 終わり ご指摘、ご質問などありましたら、コメントまでお願い致します。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iOSアプリ開発用パッケージマネージャーごとのStatic/Dynamicリンク設定とカスタマイズおよび各種調査方法

はじめに iOSアプリ開発のためのパッケージマネージャー別のリンク指定を整理しておきます。 勘違いしやすいポイントとしては、Carthageを使ってもデフォルトでdynamicリンクなのでstaticリンクするよりもアプリ起動は遅くなります。つまりCarthageを使って事前にビルドしておくことで都度のビルド時間を短縮しても、何もしないとアプリ起動が遅くなるわけです。その他、ちょっとややこしい話ですが、CocoaPodsではuse_frameworks!にするとdynamicリンクなフレームワークになりますが、これはstaticリンクにすることもできます。 この記事では、自分のアプリでどのように設定するかのプラクティスではなく、それぞれのパッケージマネージャーのデフォルトがどうなっていて、設定でそれを変える方法とビルドされたライブラリ/フレームワークが本当に意図通りになってるか確認する方法について書いておきます。 StaticやDynamicとはなにかということについてはiOSアプリ開発のための外部ライブラリのリンクの種類や配布/利用方法として別にまとめています。 私自身が勘違いしている可能性も高いし、もっといいまとめ方もあるかもしれないのでそれはそれでコメントか別にアウトプットしてもらえれば助かります。 パッケージマネージャー別のリンク 概要 Carthage デフォルト動作 dynamicリンク staticリンクにするには? 公式のドキュメントを参考 参考 https://github.com/Carthage/Carthage/blob/master/Documentation/StaticFrameworks.md CocoaPods デフォルト動作 (おそらく)staticリンク かつてSwiftがstaticリンクでのライブラリをサポートしていなかったためuse_frameworks!ができた 設定 use_frameworks! Pods v1.5.0から デフォルト動作 dynamicリンク のフレームワーク化 staticリンクにするには? use_frameworks! :linkage => :static Pods v1.9.0から ターゲットごとにstaticリンク にできる plugin プラグインでさらに細かくPodごとにも指定できる https://github.com/microsoft/cocoapods-pod-linkage 参考 https://blog.cocoapods.org/CocoaPods-1.5.0/ https://blog.cocoapods.org/CocoaPods-1.9.0-beta/ Swift Package Manager デフォルト動作 自動でstaticかdynamic かが決まる ライブラリ作成者側がPackage.swiftでtype指定し static/dynamicリンクの強制 はできる 参考 Kingfisher CocoaPodsでのstatic/dynamicリンクを混在させる Pods v1.9.0からターゲットごとにフレームワークをstaticにできるようになりました。 以下は実例。 source 'https://cdn.cocoapods.org/' target :DynamicTarget do use_frameworks! :linkage => :dynamic pod 'DynamicPod' # This will be linked dynamically end target :StaticTarget do use_frameworks! :linkage => :static pod 'StaticPod' # This will be linked statically end 参考 ライブラリが指定のリンク形式かを調べる otoolでアプリのバイナリから調べる otool ビルドされたライブラリから調べる file otoolでビルドされたアプリのバイナリからDynamic Linkしているファイルを調べる 例 $ otool -L DerivedData/実行アプリバイナリ fileコマンドで確認する 例 $ file ./Carthage/Build/iOS/ReactiveCocoa.framework/ReactiveCocoa staticリンクになってれば current ar archive dynamicリンク Mach-O dynamically linked shared library アプリの起動時間を計測する DYLD_PRINT_STATISTICS Instruments DYLD_PRINT_STATISTICSで起動時の時間表示 ライブラリ/フレームワークによってはめちゃくちゃ遅いのがstaticリンクにすることでかなり起動速度が改善したりもしますが、どのDynamicリンクが遅いのがわかる方法もあります。 Xcodeのスキーマ設定のあたりから、Environment VariablesにてDYLD_PRINT_STATISTICSに1かYESを設定してビルド後にアプリ起動すると、Xcodeのコンソールにdynamicリンク時間を計測して下記のような表示がされます。 Total pre-main time: <ここがトータル> milliseconds (100.0%) dylib loading time: 188.92 milliseconds (38.6%) rebase/binding time: 141.32 milliseconds (28.9%) ObjC setup time: 9.88 milliseconds (2.0%) initializer time: 148.68 milliseconds (30.4%) slowest intializers : libSystem.B.dylib : 3.18 milliseconds (0.6%) libMainThreadChecker.dylib : 42.47 milliseconds (8.6%) HogeHogeSample : 69.27 milliseconds (14.1%) ... : 23.87 milliseconds (4.8%) ... 例えば上記だとHogeHogeSampleというのがが遅いことがわかります。 参考 おわりに ネットとかではstaticリンクにすると起動が速くなるのでその方法がいくつか見つかりますが、現状理解、そしてInstrumentsでまずボトルネックが何かを調べるのが良さそうと思うんでリンクを載せておきます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【iOS】Metal Best Practicesの解説(14)パイプライン

Metal Best Practicesは、iOS/MacOS/tvOSのAPIであるMetalを用いた設計のベストプラクティスガイドです。 本稿では、何回かに分けてこのガイドを読み解き、コード上での実験を交えて解説していきます。 読んでそのまま理解できそうなところは飛ばしますので、原文を読みながら原文のガイドとしてご利用下さい。 また、iOSの記事なので他のOS(MacOS, tvOS)についての記載は割愛します。 他の記事の一覧は、初回記事よりご覧下さい。 Pipelines (パイプライン) ベストプラクティス:レンダーパイプラインと計算パイプラインを非同期で構築します。 render pipelineやcompute pipelineの構築は非同期に処理すれば並列性が高まりパフォーマンスが向上するという話です。 Metal Best Practiceでは、遅延読み込みを回避するために使いましょうという書き方がされていました。つまり、パイプラインの構築はコストがかかるので遅延読み込みを使いたくなるけれど、それをするぐらいなら事前に非同期に構築しておけば、初期化の際に処理をブロックしないし、必要なときには用意されているので並列性が上がりますよ、ということですね。 結論 今回は適切なサンプルが思い浮かばなかったので、コードの検証はしませんでした。大量にパイプラインを作る必要がある場合は、非同期にパイプラインを構築する手法を使えばパフォーマンスが向上しそうですね。 最後に 今回でMetal Best Practiceの解説は終わりです。 これまで読んでくださった皆さま、ありがとうございました😄 iOSを使った3D処理やAR、ML、音声処理などの作品やサンプル、技術情報を発信しています。 作品ができたらTwitterで発信していきますのでフォローをお願いします🙏 Twitterは作品や記事のリンクを貼っています。 https://twitter.com/jugemjugemjugem Qiitaは、iOS開発、とくにARや機械学習、グラフィックス処理、音声処理について発信しています。 https://qiita.com/TokyoYoshida Noteでは、連載記事を書いています。 https://note.com/tokyoyoshida Zennは機械学習が多めです。 https://zenn.dev/tokyoyoshida
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[SwiftUI]チェックボックスを自作する

投稿の経緯 現在参画中のプロジェクトがチェックボックスを必要とする仕様であり、自作したのでコードを紹介します。 環境 Swift 5.5 Xcode 13.0 コードの紹介 struct CheckBox: View { @State private var isChecked = false // チェックした/していないで変化させる var tapAction: () -> Void // CheckBoxを呼んだ時のアクション var body: some View { Button(action: { toggle() tapAction() }) { if isChecked { Image(systemName: "checkmark.square.fill") .foregroundColor(.red) } else { Image(systemName: "square") .foregroundColor(.black) } } .buttonStyle(BorderlessButtonStyle()) // Formで利用する場合に必要 } func toggle() { isChecked.toggle() UIImpactFeedbackGenerator(style: .medium) // タップでiPhoneがブルっとなる(UX向上) .impactOccurred() } } 特に難しい内容ではないと思います。参考にしてください!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【iOS】Metal Best Practicesの解説(13)関数とライブラリ

Metal Best Practicesは、iOS/MacOS/tvOSのAPIであるMetalを用いた設計のベストプラクティスガイドです。 本稿では、何回かに分けてこのガイドを読み解き、コード上での実験を交えて解説していきます。 読んでそのまま理解できそうなところは飛ばしますので、原文を読みながら原文のガイドとしてご利用下さい。 また、iOSの記事なので他のOS(MacOS, tvOS)についての記載は割愛します。 他の記事の一覧は、初回記事よりご覧下さい。 Functions and Libraries (関数とライブラリ) ベストプラクティス:関数をコンパイルし、ビルド時にあわせてライブラリをビルドします。 関数は実行時にコンパイルすると大きなコストがかかるので、ビルド時にあわせてコンパイルもしましょう、という話です。 またライブラリはなるべく単一のデフォルトライブラリにまとめましょう、ということも書かれています。 これについては永続オブジェクトでも同じことが書かれていましたね。 コードで検証してみる 実行時にビルドしたら時間がかかるのは当たり前ですが、どのぐらいのコストがかかるのか計測してみたいと思います。 サンプルコードはこちらを使用します。 実行するとこのようなシンプルな図形が表示されます。 1. 事前ビルドの場合 事前にライブラリをビルドした場合を計測してみます。 os_signpostを使用して計測します。不可をかけるために1万回ループしてライブラリを作成します。 VideoEffectMetalView.swift let log = OSLog(subsystem: "com.example.myapp", category: "Performance") os_signpost(.begin, log: log, name: "make library") for _ in 0..<10000 { guard let library = self.metalDevice.makeDefaultLibrary() else {fatalError()} } os_signpost(.end, log: log, name: "make library") 実行してみると、このあいだの処理時間は1.45秒でした。 2. 実行時ビルドの場合 実行時ビルドの場合は、シェーダーを文字列として与えます。 ちょっと長いですが、次のようにシェーダーを文字列化します。 VideoEffectMetalView.swift let shader = """ #include <metal_stdlib> using namespace metal; struct Uniforms { float time; float aspectRatio; vector_float2 touch; vector_float4 resolution; }; struct ColorInOut { float4 position [[ position ]]; float size [[point_size]]; float2 texCords; }; vertex ColorInOut simpleVertexShader( const device float4 *positions [[ buffer(0)]], const device float2 *texCords [[ buffer(1)]], constant Uniforms &uniforms [[buffer(2)]], uint vid [[ vertex_id ]] ) { ColorInOut out; out.position = positions[vid]; out.size = 5.0f; out.texCords = texCords[vid]; return out; } bool inCircle(float2 position, float2 offset, float size) { float len = length(position - offset); if (len < size) { return true; } return false; } bool inRect(float2 position, float2 offset, float size) { float2 q = (position - offset) / size; if (abs(q.x) < 1.0 && abs(q.y) < 1.0) { return true; } return false; } bool inEllipse(float2 position, float2 offset, float2 prop, float size) { float2 q = (position - offset) / prop; if (length(q) < size) { return true; } return false; } fragment float4 simpleShapeFragmentShader( ColorInOut in [[ stage_in ]], constant Uniforms &uniforms [[buffer(1)]]) { float3 destColor = float3(1.0, 1.0, 1.0); float2 position = (in.position.xy * 2.0 - uniforms.resolution.xy) / min(uniforms.resolution.x, uniforms.resolution.y); if (inCircle (position, float2( 0.1, -0.1), 0.5)) { destColor *= float3(1.0, 0.0, 0.0); } if (inRect(position, float2(0.5, -0.5), 0.25)) { destColor *= float3(0.0, 0.0, 1.0); } if (inEllipse(position, float2(-0.5, -0.3), float2(1.0, 1.0), 0.3)) { destColor *= float3(0.0, 1.0, 0.0); } return float4(destColor, 1); } """ そして、ランタイムビルドを1万回実行します。 VideoEffectMetalView.swift os_signpost(.begin, log: log, name: "make library") for _ in 0..<10000 { do { let library = try self.metalDevice.makeLibrary(source: shader, options: nil) } catch {} } os_signpost(.end, log: log, name: "make library") 実行してみると、なんと処理時間は269.49msでした。ランタイムビルドしたほうが大幅に早かったです。 結論 ということで、今回もベストプラクティスのとおりの結果にはなりませんでした。ただ、今回は計測の方法に問題があっただけであり、ベストプラクティスが否定されたわけではなさそうです。 というのも、事前ビルドしている場合は、makeDefaultLibraryを1度だけ呼べば良いわけですが、実行時にダイナミックにシェーダーを生成してビルドする場合は、その都度に必ずmakeLibraryを呼ぶ必要があるためです。そのような条件で比較した場合、事前ビルドのほうが良いパフォーマンスが出るはずです。 それと、本題とは違いますが実行時にシェーダーをビルドするというのはdo~catchで出てきたエラーを確認する必要があるので、デバッグがとても大変でした。 最後に iOSを使った3D処理やAR、ML、音声処理などの作品やサンプル、技術情報を発信しています。 作品ができたらTwitterで発信していきますのでフォローをお願いします🙏 Twitterは作品や記事のリンクを貼っています。 https://twitter.com/jugemjugemjugem Qiitaは、iOS開発、とくにARや機械学習、グラフィックス処理、音声処理について発信しています。 https://qiita.com/TokyoYoshida Noteでは、連載記事を書いています。 https://note.com/tokyoyoshida Zennは機械学習が多めです。 https://zenn.dev/tokyoyoshida
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む