TL;DR
悪いこと言わんからiOS13でSwiftUI使うのはやめとけ
はじめに
この記事は仕事でSwiftUIのアプリを実装していた際に遭遇したiOS13のバグの記録です。
iOS15もリリースされ今更iOS13を気にする場合も減っていくかと思いますが、記念碑としてまとめました。
またここに書いたものは自分が実際に出会ったバグですが、再現コード等をインターネットから引用している箇所があります。
検証環境
- Macbook Pro 13 inch Late 2013
- macOS Big Sur 11.5.2
- Xcode 13.1
- iOS Simulator
サンプルコード
明らかなバグ
iPadでActionSheetを表示しようとするとクラッシュする(13.0-13.3)
以下のようなログが出力されてクラッシュしてしまう。
2021-12-07 02:10:50.177471+0900 iOS13SwiftUIBugs[18960:1110810] *** Terminating app due to uncaught exception 'NSGenericException', reason: 'Your application has presented a UIAlertController (<UIAlertController: 0x7fbd8d840e00>) of style UIAlertControllerStyleActionSheet from _TtGC7SwiftUI19UIHostingControllerV16iOS13SwiftUIBugs11ContentView_ (<_TtGC7SwiftUI19UIHostingControllerV16iOS13SwiftUIBugs11ContentView_: 0x7fbd8bd023a0>). The modalPresentationStyle of a UIAlertController with this style is UIModalPresentationPopover. You must provide location information for this popover through the alert controller's popoverPresentationController. You must provide either a sourceView and sourceRect or a barButtonItem. If this information is not known when you present the alert controller, you may provide it in the UIPopoverPresentationControllerDelegate method -prepareForPopoverPresentation.'
*** First throw call stack:
(
0 CoreFoundation 0x00000001120dcbde __exceptionPreprocess + 350
1 libobjc.A.dylib 0x000000010e78eb20 objc_exception_throw + 48
2 UIKitCore 0x000000011b9d077e __66-[UIPopoverPresentationController presentationTransitionWillBegin]_block_invoke + 0
3 UIKitCore 0x000000011b9dac53 __71-[UIPresentationController _initViewHierarchyForPresentationSuperview:]_block_invoke + 2604
4 UIKitCore 0x000000011b9d849b __56-[UIPresentationController runTransitionForCurrentState]_block_invoke.452 + 536
5 UIKitCore 0x000000011c13485a _runAfterCACommitDeferredBlocks + 352
6 UIKitCore 0x000000011c12563c _cleanUpAfterCAFlushAndRunDeferredBlocks + 248
7 UIKitCore 0x000000011c154c6e _afterCACommitHandler + 85
8 CoreFoundation 0x000000011203eeb7 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
9 CoreFoundation 0x000000011203994e __CFRunLoopDoObservers + 430
10 CoreFoundation 0x0000000112039fca __CFRunLoopRun + 1514
11 CoreFoundation 0x00000001120396b6 CFRunLoopRunSpecific + 438
12 GraphicsServices 0x0000000113c7abb0 GSEventRunModal + 65
13 UIKitCore 0x000000011c12ba67 UIApplicationMain + 1621
14 libswiftUIKit.dylib 0x000000011159335b $s5UIKit17UIApplicationMainys5Int32VAD_SpySpys4Int8VGGSgSSSgAJtF + 155
15 iOS13SwiftUIBugs 0x000000010de22208 $sSo21UIApplicationDelegateP5UIKitE4mainyyFZ + 104
16 iOS13SwiftUIBugs 0x000000010de2218c $s16iOS13SwiftUIBugs11AppDelegateC5$mainyyFZ + 28
17 iOS13SwiftUIBugs 0x000000010de22288 main + 24
18 libdyld.dylib 0x0000000112f8ecf5 start + 1
19 ??? 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSGenericException', reason: 'Your application has presented a UIAlertController (<UIAlertController: 0x7fbd8d840e00>) of style UIAlertControllerStyleActionSheet from _TtGC7SwiftUI19UIHostingControllerV16iOS13SwiftUIBugs11ContentView_ (<_TtGC7SwiftUI19UIHostingControllerV16iOS13SwiftUIBugs11ContentView_: 0x7fbd8bd023a0>). The modalPresentationStyle of a UIAlertController with this style is UIModalPresentationPopover. You must provide location information for this popover through the alert controller's popoverPresentationController. You must provide either a sourceView and sourceRect or a barButtonItem. If this information is not known when you present the alert controller, you may provide it in the UIPopoverPresentationControllerDelegate method -prepareForPopoverPresentation.'
terminating with uncaught exception of type NSException
CoreSimulator 776.4 - Device: iPad mini 5th(13.0) (045FAB2E-2DDF-49FF-93DD-9F1611540BB5) - Runtime: iOS 13.0 (17A577) - DeviceType: iPad mini (5th generation)
ログを見た限りではUIKitでActionSheetを表示する際にsourceViewを指定し忘れたときと同じ現象が起きていると推察される。
[Swift]iPadのActionSheet表示でクラッシュする問題 | RE:ENGINES
(正直UIKitのこの仕様も微妙だし、Apple自身がSwiftUIを実装する中でこの罠にハマってしまっている)
回避方法
Method Swizzlingを用いてUIAlertControllerのイニシャライザを書き換え強制的にAlertとして表示させる
参考: [SwiftUI]iPadでActionSheetが使えないという事実をねじ伏せる - Qiita
GeometryProxyのデータにアクセスするとクラッシュする場合がある(13.0-13.2)
以下のようなログを出力してクラッシュしてしまう。
2021-12-07 02:25:28.798110+0900 iOS13SwiftUIBugs[19408:1164577] precondition failure: attribute failed to set an initial value: 86
CoreSimulator 776.4 - Device: 8(13.0) (CEA2DCAB-4288-4ECE-AF94-F31CCBDB2DBF) - Runtime: iOS 13.0 (17A577) - DeviceType: iPhone 8
AttributeGraph precondition failure: attribute failed to set an initial value: 86.
回避法
おそらくなし…
AnyViewを挟むと回避できるといった情報もあるが、Viewがうまく表示されなかったりする…
Alert表示中に別のAlertを表示してしまうとクラッシュする(13.0-13.2, 13.4-13.7)
backtraceでSwiftUI.AlertBridge.preferencesDidChange()などが表示される。
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
frame #0: 0x000000010a799bc2 SwiftUI`SwiftUI.AlertBridge.preferencesDidChange(SwiftUI.PreferenceList) -> () + 2866
frame #1: 0x000000010a265b73 SwiftUI`SwiftUI._UIHostingView.preferencesDidChange() -> () + 387
frame #2: 0x000000010a36125a SwiftUI`SwiftUI.ViewGraph.updateOutputs(at: SwiftUI.Time) -> () + 234
frame #3: 0x000000010a6b76ea SwiftUI`closure #1 () -> () in closure #1 () -> () in SwiftUI.ViewRendererHost.render(interval: Swift.Double, updateDisplayList: Swift.Bool) -> () + 122
frame #4: 0x000000010a6b746c SwiftUI`closure #1 () -> () in SwiftUI.ViewRendererHost.render(interval: Swift.Double, updateDisplayList: Swift.Bool) -> () + 636
frame #5: 0x000000010a6b4d47 SwiftUI`SwiftUI.ViewRendererHost.render(interval: Swift.Double, updateDisplayList: Swift.Bool) -> () + 439
frame #6: 0x000000010a84f472 SwiftUI`SwiftUI._UIHostingView.layoutSubviews() -> () + 226
frame #7: 0x000000010a84f495 SwiftUI`@objc SwiftUI._UIHostingView.layoutSubviews() -> () + 21
frame #8: 0x0000000116bf3722 UIKitCore`-[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 2478
frame #9: 0x000000010fb73ef9 QuartzCore`-[CALayer layoutSublayers] + 255
frame #10: 0x000000010fb788ff QuartzCore`CA::Layer::layout_if_needed(CA::Transaction*) + 517
frame #11: 0x000000010fb84fe4 QuartzCore`CA::Layer::layout_and_display_if_needed(CA::Transaction*) + 80
frame #12: 0x000000010facd4a8 QuartzCore`CA::Context::commit_transaction(CA::Transaction*, double) + 324
frame #13: 0x000000010fb02ab3 QuartzCore`CA::Transaction::commit() + 643
frame #14: 0x0000000116748cb9 UIKitCore`_afterCACommitHandler + 160
frame #15: 0x000000010c7cfeb7 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
frame #16: 0x000000010c7ca94e CoreFoundation`__CFRunLoopDoObservers + 430
frame #17: 0x000000010c7cafca CoreFoundation`__CFRunLoopRun + 1514
frame #18: 0x000000010c7ca6b6 CoreFoundation`CFRunLoopRunSpecific + 438
frame #19: 0x000000010e38bbb0 GraphicsServices`GSEventRunModal + 65
frame #20: 0x000000011671fa67 UIKitCore`UIApplicationMain + 1621
frame #21: 0x000000010990835b libswiftUIKit.dylib`UIKit.UIApplicationMain(Swift.Int32, Swift.Optional<Swift.UnsafeMutablePointer<Swift.UnsafeMutablePointer<Swift.Int8>>>, Swift.Optional<Swift.String>, Swift.Optional<Swift.String>) -> Swift.Int32 + 155
frame #22: 0x0000000108418208 iOS13SwiftUIBugs`static UIApplicationDelegate.main() at <compiler-generated>:0
* frame #23: 0x000000010841818c iOS13SwiftUIBugs`static AppDelegate.$main(self=iOS13SwiftUIBugs.AppDelegate) at AppDelegate.swift:10:1
frame #24: 0x0000000108418288 iOS13SwiftUIBugs`main at <compiler-generated>:0
frame #25: 0x000000010d69fcf5 libdyld.dylib`start + 1
frame #26: 0x000000010d69fcf5 libdyld.dylib`start + 1
SwiftUI crash in AlertBridge.preferencesDidChange(_:) | Apple Developer Forums
回避方法
同時にAlertを表示してしまわないように頑張る…
(表示しようとしたときにrootViewControllerから辿ってpresentedViewControllerがUIAlertControllerならdismissするなど)
バグらしきもの
以降は仕様の可能性があるものの、のちのバージョンで挙動が変わったことからバグとも考えられるものです。
ButtonのlabelにImageを設定すると水色に塗りつぶされる(13.0-13.2, 13.4-13.7)
対処法
.renderingMode(.original)
をつける
iPadでPopoverのサイズが固定になってしまう(13.0-13.3)
13.0 | 13.4 |
---|---|
対処法
おそらくなし…
おわりに
SwiftUIは宣言的にUIを記述でき優れた点も多いですが、課題も未だ多いフレームワークです。
その課題を気にしなくていいか、対処できる場合に使うことをおすすめします。