iOS13 SwiftUIバグ集

## 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

#### サンプルコード

[trickart/iOS13SwiftUIBugs](https://github.com/trickart/iOS13SwiftUIBugs)

## 明らかなバグ

### 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](https://re-engines.com/2017/11/01/swiftipad%E3%81%AEactionsheet%E8%A1%A8%E7%A4%BA%E3%81%A7%E3%82%AF%E3%83%A9%E3%83%83%E3%82%B7%E3%83%A5%E3%81%99%E3%82%8B%E5%95%8F%E9%A1%8C/)

(正直UIKitのこの仕様も微妙だし、Apple自身がSwiftUIを実装する中でこの罠にハマってしまっている)

#### 回避方法

Method Swizzlingを用いてUIAlertControllerのイニシャライザを書き換え強制的にAlertとして表示させる

参考: [[SwiftUI]iPadでActionSheetが使えないという事実をねじ伏せる - Qiita](https://qiita.com/noppefoxwolf/items/f09f0d7122f89f67bc5e)

### 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がうまく表示されなかったりする…  

[How to track down "precondition failure: attribute failed to set an initial value: ???" errors? | Apple Developer Forums](https://developer.apple.com/forums/thread/129171)

### 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](https://developer.apple.com/forums/thread/665875)

#### 回避方法

同時にAlertを表示してしまわないように頑張る…
(表示しようとしたときにrootViewControllerから辿ってpresentedViewControllerがUIAlertControllerならdismissするなど)

## バグらしきもの

以降は仕様の可能性があるものの、のちのバージョンで挙動が変わったことからバグとも考えられるものです。

### ButtonのlabelにImageを設定すると水色に塗りつぶされる(13.0-13.2, 13.4-13.7)

<img width="375" alt="button_render_mode.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61625/9cedf7fa-0aad-02e6-1935-61e3389caf90.png">

#### 対処法

`.renderingMode(.original)` をつける

### iPadでPopoverのサイズが固定になってしまう(13.0-13.3)

| 13.0 | 13.4 |
| ---- | ---- |
| ![popover_13.0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61625/c99f1e08-4c39-0275-52ae-eccf12df3056.png) | ![popover_13.4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61625/328cc324-a030-10ec-8149-5952541dc2fe.png) |

#### 対処法

おそらくなし…

## おわりに

SwiftUIは宣言的にUIを記述でき優れた点も多いですが、課題も未だ多いフレームワークです。
その課題を気にしなくていいか、対処できる場合に使うことをおすすめします。