Open59

iOS開発

saharasahara

デバッグ

https://opensource.apple.com/source/lldb/lldb-310.2.36/www/tutorial.html

XcodeのLLDBデバッグでよく使う技 - しおメモ

iOSアプリ開発に便利なLLDBのp/po/vコマンドを覚えよう! - Takahiro Octopress Blog

LLDB: Beyond "po" - WWDC 2019 - Videos - Apple Developer

https://lldb.llvm.org/use/tutorial.html

https://qiita.com/kazuhiro4949/items/3d0b33b6738bdfe475fb

LLDB

スレッドのリストを表示するlist

(lldb) thread list
Process 11439 stopped
* thread #1: tid = 0x171b4, 0x00000001025253f9 Teachme Biz`closure #1 in FooPresenter.didTaskManualDismiss(self=0x00007fbb598aa000, isTaskManualFinished=false) at FooPresenter.swift:232:24, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  thread #5: tid = 0x176ce, 0x00000001187d196a libsystem_kernel.dylib`mach_msg_trap + 10, name = 'com.apple.uikit.eventfetch-thread'
  thread #11: tid = 0x177fc, 0x00000001187d196a libsystem_kernel.dylib`mach_msg_trap + 10, name = 'com.apple.NSURLConnectionLoader'
  thread #43: tid = 0x1aecc, 0x00000001187d196a libsystem_kernel.dylib`mach_msg_trap + 10
  thread #44: tid = 0x1aecd, 0x00000001187d304a libsystem_kernel.dylib`__workq_kernreturn + 10
  thread #45: tid = 0x1aee0, 0x00000001187d304a libsystem_kernel.dylib`__workq_kernreturn + 10
  thread #51: tid = 0x1aee5, 0x00000001187d304a libsystem_kernel.dylib`__workq_kernreturn + 10
  thread #52: tid = 0x1aee6, 0x00000001187d304a libsystem_kernel.dylib`__workq_kernreturn + 10
  thread #53: tid = 0x1af4d, 0x00000001187d304a libsystem_kernel.dylib`__workq_kernreturn + 10
  thread #54: tid = 0x1af4f, 0x00000001187d304a libsystem_kernel.dylib`__workq_kernreturn + 10
  thread #55: tid = 0x1af50, 0x00000001187d304a libsystem_kernel.dylib`__workq_kernreturn + 10
  thread #56: tid = 0x1af51, 0x00000001187d304a libsystem_kernel.dylib`__workq_kernreturn + 10
  thread #57: tid = 0x1af53, 0x00000001187d304a libsystem_kernel.dylib`__workq_kernreturn + 10
  thread #58: tid = 0x1af52, 0x00000001187d304a libsystem_kernel.dylib`__workq_kernreturn + 10
  thread #59: tid = 0x1af54, 0x00000001187d304a libsystem_kernel.dylib`__workq_kernreturn + 10
  thread #60: tid = 0x1af7f, 0x00000001187d304a libsystem_kernel.dylib`__workq_kernreturn + 10

スタックトレースを表示する backtrace

(lldb) thread backtrace
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x00000001025253f9 Teachme Biz`closure #1 in FooPresenter.didTaskManualDismiss(self=0x00007fbb598aa000, isTaskManualFinished=false) at FooPresenter.swift:232:24
    frame #1: 0x0000000102525db0 Teachme Biz`partial apply for closure #1 in FooPresenter.didTaskManualDismiss(isTaskManualFinished:) at <compiler-generated>:0
    frame #2: 0x00000001025265e0 Teachme Biz`thunk for @escaping @callee_guaranteed @Sendable @async () -> (@out A) at <compiler-generated>:0
    frame #3: 0x0000000102526710 Teachme Biz`partial apply for thunk for @escaping @callee_guaranteed @Sendable @async () -> (@out A) at <compiler-generated>:0
saharasahara

Deep Link

ディープリンク技術についてまとめてみた - Qiita

Universal Links

iOS9でUniversal Linksが導入されたときの記事

https://qiita.com/mono0926/items/2bf651246714f20df626

https://web.archive.org/web/20160607225722/http://blog.hokolinks.stfi.re/how-to-implement-apple-universal-links-on-ios-9/?sf=kjlnv)#aa

https://qiita.com/keroppi0_0/items/1a7fab4a8cc78412d30c

https://qiita.com/orimomo/items/f7b2acd12bf8d26d48fe

What's new in Universal Links - WWDC20 - Videos - Apple Developer

Supporting Universal Links in Your App | Apple Developer Documentation

Support Universal Links - App Search Programming Guide - Apple Documentation

エントリーポイント

application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool

application(_:didFinishLaunchingWithOptions:)

※launchOptionsのキーに.userActivityDictionaryを指定して、その中身からuserActivityの内容がとれます。

application(_:continueUserActivity:restorationHandler:)

push notification

didReceiveRemoteNotification: fetchCompletionHandler
or
userNotificationCenter(_:didReceive:withCompletionHandler:)

saharasahara

EmptyView

Overview

Designing For The Empty States

DZNEmptyDataSet

dzenbot/DZNEmptyDataSet: A drop-in UITableView/UICollectionView superclass category for showing empty datasets whenever the view has no content to display

It is (extremely) important to set the dataSetSource and dataSetDelegate to nil, whenever the view is going to be released. This class uses KVO under the hood, so it needs to remove the observer before dealocating the view.

https://www.cocoacontrols.com/controls/dznemptydataset

saharasahara

AVFoundation

https://qiita.com/falcon0328/items/9373a5008df64f9608b4

https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/AVFoundationPG/Articles/00_Introduction.html

https://qiita.com/yakimeron/items/34a65397c1041c0b2a0c

AVFoundation | Apple Developer Documentation

Media Assets and Metadata | Apple Developer Documentation

Sample-level Reading and Writing | Apple Developer Documentation

Media Playback and Selection | Apple Developer Documentation

Video Settings | Apple Developer Documentation

Media Composition and Editing | Apple Developer Documentation

Using Swift 5 asynchronous feature

https://developer.apple.com/documentation/avfoundation/media_assets_and_metadata/loading_media_data_asynchronously

AVAsset

https://developer.apple.com/documentation/avfoundation/avasset

AVKit

AVKit | Apple Developer Documentation

Core Media

https://developer.apple.com/documentation/coremedia

AVPlayer

addPeriodicTimeObserver(forInterval:queue:using:) | Apple Developer Documentation

seek(to:) | Apple Developer Documentation

動画を再生する

Creating a Movie Player App with Basic Playback Controls | Apple Developer Documentation

動画を最後まで再生したら最初に戻す

Notificationで制御する。

https://developer.apple.com/documentation/foundation/notificationcenter/1411723-addobserver

        private var playToEndTimeObserver: NSObjectProtocol?

        playToEndTimeObserver = NotificationCenter.default
            .addObserver(
                forName: .AVPlayerItemDidPlayToEndTime,
                object: nil, queue: .main) { [weak self] _ in
            self?.player?.currentItem?.seek(to: CMTime.zero, completionHandler: nil)
        }

asyncで書く方法もあるがここでは割愛。

Meet AsyncSequence

saharasahara

Networking

Choosing a Network Debugging Tool

        // handle HTTP server-side error
        if let responseString = String(bytes: responseBody, encoding: .utf8) {
            // The response body seems to be a valid UTF-8 string, so print that.
            print(responseString)
        } else {
            // Otherwise print a hex dump of the body.
            print(responseBody as NSData)
        }

String(bytes: foo, encoding: .utf8) こんなAPIあったのか。

saharasahara

UITableView

deselectRow

https://egg-is-world.com/2020/07/11/tableview-deselect/

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    if let indexPath = tableView.indexPathForSelectedRow {
        tableView.deselectRow(at: indexPath, animated: true)
    }
}

deselectRow(at:animated:) | Apple Developer Documentation

https://qiita.com/takashings/items/36b820f09fb19edd7556

Resizing Cells

https://cockscomb.hatenablog.com/entry/uitextview-on-uitableview

Auto Layout Guide: Working with Self-Sizing Table View Cells

saharasahara

Xcode13・iOS15

https://zenn.dev/ariiyu/scraps/0e309cc095cd3f

UINavigationBarAppearance

Xcode13でビルドすると、ナビゲーションバーの背景色が意図しない挙動になる。

https://developer.apple.com/documentation/uikit/uinavigationbarappearance

https://shtnkgm.com/blog/2021-08-18-ios15.html#ナビゲーションバー-ステータスバー-が透明になる、背景色が適用されない

https://blog.personal-factory.com/2020/05/04/customize-navigationbar-in-ios13/

    //objective-C
    if (@available(iOS 15.0, *)) {
        UINavigationBarAppearance *appearance = [UINavigationBarAppearance new];
        appearance.backgroundColor = UIColor.whiteColor;
        UINavigationBar.appearance.standardAppearance = appearance;
        UINavigationBar.appearance.scrollEdgeAppearance = appearance;
    }

Customizing the appearance of UINavigationBar

https://developer.apple.com/documentation/technotes/tn3106-customizing-uinavigationbar-appearance

UITableViewの意図しないマージンの発生

https://developer.apple.com/forums/thread/683980

saharasahara

Size Class

uitraitcollection

setoverridetraitcollection

When implementing a custom container view controller, you can use this method to change the traits of any embedded child view controllers to something more appropriate for your layout. Making such a change alters other view controller behaviors associated with that child. For example, modal presentations behave differently in a horizontally compact versus horizontally regular environment.

init(traitsFrom:) | Apple Developer Documentation

If the array contains more than one element, the highest-indexed element that contains a given trait is used for that trait.

traitCollection

UIScreen, UIWindow, UIViewController, UIPresentationController, UIViewはUITraitCollectionクラスのインスタンスを traitCollectionプロパティとして保持している。

UITraitCollectionは、 horizontalSizeClass, verticalSizeClass, displayScale, userInterfaceIdiomの情報を持っている。

saharasahara

Dynamic Type

UIContentSizeCategory | apple

設定値の変化の検出

https://developer.apple.com/documentation/uikit/uitraitenvironment

https://developer.apple.com/documentation/uikit/uitraitcollection

https://developer.apple.com/documentation/uikit/uiapplication/1623048-preferredcontentsizecategory

現在の設定値は

UIApplication.shared.preferredContentSizeCategory

で取得できる。

設定値は、

  • 設定アプリ> アクセシビリティ 内で設定する
  • Control Centerで文字サイズの設定をする

から変更できる。

この変更の検出が、

ユーザーに選択されたDynamic Type設定はUIContentSizeCategoryという型で、UIApplication.shared.preferredContentSizeCategoryまたは(iOS 8以上では)traitCollection.preferredContentSizeCategoryで取得できます。現時点(iOS 12)で利用できるすべての値は以下のとおりです。

アプリがバックグラウンドにある間に、ユーザーがDynamic Typeの設定を変えると…

  • UIContentSizeCategory.didChangeNotificationというnotificationがアプリに送られます。
  • iOS 8以上ではtraitCollectionDidChangeメソッドが呼ばれます。UITraitEnvironmentプロトコルのメソッドですが、UIViewやUIViewControllerが実装している…

https://techlife.cookpad.com/entry/2019/01/08/090000

のように行うことが出来る。

留意点

Control Centerで文字サイズの設定をする際に、「(アプリ名)のみ」 を選ぶと、設定アプリ> アクセシビリティ 内で設定する文字サイズがそのアプリに反映されなくなる。

control-center

setting-app

saharasahara

Mock

https://logmi.jp/tech/articles/322551

Mockolo

https://blog.andooown.dev/post/2020/12/mockolo/

Mockoloがうまく動かなくなった件

時間がかかったので備忘録を残しておく。

前提

macOS12.5
Xcode12.4.1をXcodes経由で使用

経緯

元々、XcodeのBuild Phasesでmockoloでモック生成を行っていたのが、最近GeneratedMocks.swiftが削除されてままになりビルドエラーになる事象が発生していた。

応急処置として、スクリプト中のGeneratedMocks.swiftを削除する部分をコメントアウトしていた。

今回、GeneratedMocks.swiftを更新しなければいけない状況になったため、重い腰を上げて取り組むことになった。

やったこと

  1. AppStore経由でXcodeをインストール

Xcodes経由で導入したXcode(Xcode-13.4.1.appなど)では、うまく動かないという報告があったため。

  1. コマンドラインツールを上で導入したXcodeのものに変更
$ xcode-select -print-path
/Applications/Xcode-13.4.1.app/Contents/Developer
$ sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
$ xcode-select -print-path
/Applications/Xcode.app/Contents/Developer
  1. mint経由でmockoloを入れ直す
$ mint list
🌱 Installed mint packages:
  SwiftLint
    - 0.46.2
    - 0.47.1
  mockolo
    - 1.6.3
    - 1.7.0
$ mint uninstall mockolo
🌱 mockolo was uninstalled
$ mint bootstrap
🌱 Cloning mockolo 1.7.0
🌱 Resolving package
🌱 Building package
Building for production...
[1/7] Copying lib_InternalSwiftSyntaxParser.dylib
[2/7] Compiling TSCclibc process.c
[3/7] Compiling TSCclibc libc.c
remark: Incremental compilation has been disabled: it is not compatible with whole module optimizationremark: Incremental compilation has been disabled: it is not compatible with whole module optimization[4/9] Compiling atomic-counter.c
remark: Incremental compilation has been disabled: it is not compatible with whole module optimization[5/10] Compiling TSCLibc libc.swift
remark: Incremental compilation has been disabled: it is not compatible with whole module optimization[6/11] Compiling ArgumentParserToolInfo ToolInfo.swift
remark: Incremental compilation has been disabled: it is not compatible with whole module optimization[7/12] Compiling TSCBasic Await.swift
remark: Incremental compilation has been disabled: it is not compatible with whole module optimization[8/13] Compiling ArgumentParser BashCompletionsGenerator.swift
[9/13] Compiling TSCUtility Archiver.swift
[10/13] Compiling SwiftSyntax AbsolutePosition.swift
<unknown>:0: error: error opening '/private/var/folders/dx/hbysrw7j7r7gt5p_rjvsgzfw0000gp/T/mint/github.com_uber_mockolo/.build/arm64-apple-macosx/release/SwiftSyntax.swiftmodule' for output: No such file or directory
<unknown>:0: error: error opening '/private/var/folders/dx/hbysrw7j7r7gt5p_rjvsgzfw0000gp/T/mint/github.com_uber_mockolo/.build/arm64-apple-macosx/release/SwiftSyntax.build/SwiftSyntax.d' for output: No such file or directory
warning: next compile won't be incremental; could not write build record to /private/var/folders/dx/hbysrw7j7r7gt5p_rjvsgzfw0000gp/T/mint/github.com_uber_mockolo/.build/arm64-apple-macosx/release/SwiftSyntax.build/master.swiftdepserror: stat error: No such file or directory (2)
error: error: accessing build database "/private/var/folders/dx/hbysrw7j7r7gt5p_rjvsgzfw0000gp/T/mint/github.com_uber_mockolo/.build/arm64-apple-macosx/build.db": disk I/O error
🌱 Encountered error during "swift build -c release -Xswiftc -target -Xswiftc arm64-apple-macosx12.5". Use --verbose to see full output
🌱  Failed to build mockolo 1.7.0 with SPM
$ mint bootstrap          
🌱 Cloning mockolo 1.7.0
🌱 Resolving package
🌱 Building package
🌱 Installed mockolo 1.7.0
🌱 Installed 1/2 packages

上記の通り、1回目のmint bootstrapは失敗したものの、めげずにmint bootstrapを繰り返すことでmockoloのインストールは成功した。

これはmint or mockoloの側の修正が必要な気もするけど一旦先に進む。

  1. ターミナルから、モック生成のコマンドを実行
$ mint run uber/mockolo mockolo -s "${SRCROOT}/Classes" -s "${SRCROOT}/Domain" -s "${SRCROOT}/Application" -d "${SRCROOT}/Classes/Developer/Stub/GeneratedMocks.swift" --macro "DEBUG"
Start...
["Process input mock files..."]
["Took", 7.200241088867188e-05]
["Process source files and generate an annotated/protocol map..."]
MockoloFramework/FileScanner.swift:136: Fatal error: Failed to traverse file:///Classes/ with error Error Domain=NSCocoaErrorDomain Code=260 "The file “Classes” couldn’t be opened because there is no such file." UserInfo={NSURL=file:///Classes/, NSFilePath=/Classes, NSUnderlyingError=0x6000019d5920 {Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"}}.
zsh: trace trap  mint run uber/mockolo mockolo -s "${SRCROOT}/Classes" -s "${SRCROOT}/Domain"

エラーが出た旨の表示はなされるものの、GeneratedMocks.swiftの更新自体はできており、ビルドも再びできるようになったので良しとしている。

saharasahara

Appcode

Keyboard shortcut

やりたいこと キーボードショートカット
マウスで複数箇所にキャレットを指定 ⌥ Shift クリック

単体テストを実行する

Configurationを編集して、targetを追加する必要がある。

メニューバーのConfigurationのプルダウンメニューから、[Edit Configurations...]を選択

Edit Configuration

Run/Debug Configurationsウインドウで、+ボタン(Add New Configuration)> [XCTest]を選択

add xctest configuration

ここまでで、XCTest の All TestsのConfigurationが作成されています。

このとき、xcodeprojフォルダ内に差分が生じています。この差分は削除してもAppCodeの単体テストには影響しません。

Run/Debug Configurationsウインドウを閉じて、メニューバーのConfigurationのプルダウンメニューから[All Tests]を選択します。

シミュレーター実行時と同様に、Run 'All Tests' ( Ctrl R ) や Debug 'All Tests' ( Ctrl D )で単体テストを実行できます。

saharasahara

CI

https://blog.ch3cooh.jp/entry/20150210/1423573065#ビルド

Swift Package ManagerをXcodeから利用した際に、CI周りの設定をどうするか

https://techlife.cookpad.com/entry/2022/06/01/090000

ローカルでパッケージを更新した際に、Github Actionsのステップがxcodebuildコマンドを使うところでコケる。

Run scheme_list=$(xcodebuild -list -json -disableAutomaticPackageResolution | tr -d "\n")
  scheme_list=$(xcodebuild -list -json -disableAutomaticPackageResolution | tr -d "\n")
  default=$(echo $scheme_list | ruby -e "require 'json'; puts JSON.parse(STDIN.gets)['project']['targets'][0]")
  echo $default | cat >default
  echo Using default scheme: $default
  shell: /bin/bash -e {0}
xcodebuild: error: Could not resolve package dependencies:
  Package.resolved file is corrupted or malformed; fix or delete the file to continue: unsupported schema version 2
saharasahara

UILabel

タイトルと境界の間の余白を設定する

https://softmoco.com/basics/how-to-add-padding-to-uilabel-and-uitextfield.php

https://qiita.com/ryokosuge/items/d5d291129da1bc8baea6

@IBDesignable class CustomLabel: UILabel {
    
    @IBInspectable var topPadding: CGFloat = 10
    @IBInspectable var bottomPadding: CGFloat = 10
    @IBInspectable var leftPadding: CGFloat = 10
    @IBInspectable var rightPadding: CGFloat = 10
    
    override func drawText(in rect: CGRect) {
        super.drawText(in: rect.inset(by: UIEdgeInsets.init(top: topPadding, left: leftPadding, bottom: bottomPadding, right: rightPadding)))
    }
    
    override var intrinsicContentSize: CGSize {
        var size = super.intrinsicContentSize
        size.height += (topPadding + bottomPadding)
        size.width += (leftPadding + rightPadding)
        return size
    }
}

できるだけ幅を広げる

http://devetc.org/code/2014/07/07/auto-layout-and-views-that-wrap.html

https://scrapbox.io/tasuwo-ios/複数行対応のUIButtonを作成する

https://scrapbox.io/tasuwo-ios/preferredMaxLayoutWidth

saharasahara

Widget

https://developer.apple.com/jp/widgets/

https://qiita.com/m-inada0408/items/c17863508fd4697d51fd

Meet WidgetKit

https://developer.apple.com/videos/play/wwdc2020/10028/

Widgetの表示がアップデートされる機構の説明。

update widget

HIG

https://developer.apple.com/design/human-interface-guidelines/components/system-experiences/widgets/

In iOS and iPadOS, widgets appear on the Home Screen or in Today View; in macOS, Notification Center displays widgets. Widgets can be small, medium, large, and — in iPadOS only — extra large.

せっかくだから extra largeやりたいなあ。

iOS 16 introduces widgets on the Lock Screen, which let people place rich, glanceable content on their iPhone Lock Screen in a way that’s similar to placing a complication on their Apple Watch face. Lock Screen widgets are also similar to complications in both design and implementation. For guidance, see Platform considerations below.

widgets on the Lock Screenも面白そう。Apple WatchのComplicationに似たものと書いてある。

Creating a Widget Extension (developer.apple.com article)

https://developer.apple.com/documentation/widgetkit/creating-a-widget-extension

更新のタイミング

https://developer.apple.com/jp/documentation/widgetkit/keeping-a-widget-up-to-date/

疑問点

  • 更新のタイミング
     - 新着・更新コーナーの表示に耐えうるものなのか
     - Lock screen版では違いがあるのか
saharasahara

画面共有

AirPlayを使って、Macの画面を他のMacから操作できるようにする機能を試した。

https://dev.classmethod.jp/articles/connecting-a-second-external-extended-display-to-another-macbook-device-using-airplay-on-the-m1-macbook/

Intel Macbook ProからReceiverのM1 Pro Macに接続する場合、Receiver側のMacが外部ディスプレイに接続していたとしてもSender側が拡張ディスプレイとして使えるのは1枚のディスプレイだけであった。

コレジャナイ感。

Universal Control

そういえば、同じApple IDでログインしているApple デバイスであれば、カーソル共有する機能が入ったはず。
それがUnivesal Control。

システム設定アプリのディスプレイから設定できたはずなのだが、なぜかメニューが出てこないのだ。Onにしているはずなのに。

appleのサポートページ

https://support.apple.com/ja-jp/102459

によると、インターネット共有をOffにしないといけないらしい。開発用のMacはVPNを検証端末に共有する都合上、インターネット共有をOnにしないといけない。

まあ、今回はこの設定変更には目を瞑ろう。

がしかし、個人用Macからは接続できず、新しい開発用M1 Pro Macからのみ接続することができた。
すぐに個人用Macからも接続できるようになった。