SwiftUI CollectionView 包装器无法从 updateUIViewController 应用快照

Posted

技术标签:

【中文标题】SwiftUI CollectionView 包装器无法从 updateUIViewController 应用快照【英文标题】:SwiftUI CollectionView wrapper cannot apply snapshot from updateUIViewController 【发布时间】:2020-05-01 18:57:39 【问题描述】:

我已经实现了这样的 UICollectionView 包装器

struct CollectionView: UIViewControllerRepresentable 

    // MARK: - Properties
    let layout: UICollectionViewLayout
    let sections: [Section]
    let items: [Section: [Item]]

    // MARK: - Actions
    let content: (_ indexPath: IndexPath, _ item: Item) -> AnyView

    init(layout: UICollectionViewLayout,
         sections: [Section],
         items: [Section: [Item]],
         @ViewBuilder content:  @escaping (_ indexPath: IndexPath, _ item: Item) -> AnyView) 
        self.layout = layout

        self.sections = sections
        self.items = items

        self.content = content
    

    func makeUIViewController(context: Context) -> CollectionViewController 

        let controller = CollectionViewController()
        controller.layout = layout
        controller.content = content
        controller.snapshot = snapshotForCurrentState()

        controller.collectionView.delegate = context.coordinator

        return controller
    

    func updateUIViewController(_ controller: CollectionViewController, context: Context) 

        controller.snapshot = snapshotForCurrentState()
        controller.reloadDataSource()
    


    func makeCoordinator() -> Coordinator 
        Coordinator(self)
    

这里是 CollectionViewController 的代码

class CollectionViewController: UIViewController 

    var layout: UICollectionViewLayout! = nil
    var snapshot: NSDiffableDataSourceSnapshot<Section, Item>! = nil
    var content: ((_ indexPath: IndexPath, _ item: Item) -> AnyView)! = nil

    let queue = DispatchQueue(label: "diffQueue")

    lazy var dataSource: UICollectionViewDiffableDataSource<Section, Item> = 
        let dataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView, cellProvider: cellProvider)
        return dataSource
    ()

    lazy var collectionView: UICollectionView = 
        let collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
        collectionView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
        collectionView.backgroundColor = .red //.clear
        return collectionView
    ()

    var isLoaded: Bool = false

    override func viewDidLoad() 
        super.viewDidLoad()

        configureCollectionView()

        // load initial data
        reloadDataSource()

        isLoaded = true
    


extension CollectionViewController 

    func reloadDataSource(animating: Bool = false) 

        dataSource.apply(snapshot, animatingDifferences: animating) 
            print("applying snapshot completed!")
        
    


extension CollectionViewController 

    private func configureCollectionView() 
        view.addSubview(collectionView)

        collectionView.register(HostingControllerCollectionViewCell<AnyView>.self, forCellWithReuseIdentifier: HostingControllerCollectionViewCell<AnyView>.reuseIdentifier)

        collectionView.delegate = self

        print("configured collection view")
    

    private func cellProvider(collectionView: UICollectionView, indexPath: IndexPath, item: Item) -> UICollectionViewCell? 

        print("providing cell for \(indexPath)...")
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: HostingControllerCollectionViewCell<AnyView>.reuseIdentifier, for: indexPath) as? HostingControllerCollectionViewCell<AnyView> else 
            fatalError("Could not load cell")
        

        //cell.host(AnyView(Text(item.title)))
        cell.host(content(indexPath, item))

        return cell
    

它有效,但我只能在 viewDidLoad 调用 reloadDatasource() 中加载数据源,如果然后从 UIViewControllerRepresentable 中的 updateUIViewController 再次调用此方法,则集合视图为空。

这里是这个示例 CollectionView 包装器的完整存储库 https://github.com/michzio/SwifUICollectionView

更新

我注意到我收到了这个错误

2020-05-02 07:18:20.891685+0200 SwiftUICollectionView[39727:58991470] [CollectionView] Layout attributes <UICollectionViewLayoutAttributes: 0x7fbd6f30b270> index path: (<NSIndexPath: 0xd64d621a2e1d9027> length = 2, path = 0 - 37); frame = (187.667 792; 187.667 44);  were received from the layout <UICollectionViewCompositionalLayout: 0x7fbd6df04920> but are not valid for the data source counts. Attributes will be ignored.

我还注意到,如果我更改 updateUIViewController

controller.reloadDataSource(animating: false)

controller.reloadDataSource(animating: true)

显示单元格。但是在点击手势上强制执行连续刷新会使 UI 挂起(我认为可能有太多项目并且计算动画有问题,但即使我将项目数量减少到 1000 也不会冻结。)

更新 2

现在动画时:reloadDataSource() 中的 true(来自 updateUIViewController)我可能会崩溃,但只有 EXC_BAD_ARITHM 没有信息

如果我删除 CollectionViewController.viewDidLoad() 中的初始数据加载,那么只会调用 updateUIViewController 并且这里会发生另一个奇怪的异常

* -[UICollectionView _dequeueReusableViewOfKind:withIdentifier:forIndexPath:viewCategory:] 中的断言失败,/Library/Caches/com.apple.xbs/Sources/UIKitCore_Sim/UIKit-3920.26.113/UICollectionView.m:5971 2020-05-02 07:33:02.037804+0200 SwiftUICollectionView[40091:59004149] * 由于未捕获的异常“NSInternalInconsistencyException”而终止应用程序,原因:“无法使视图出队 种类:带有标识符 ItemCell 的 UICollectionElementKindCell - 必须 为标识符注册一个 nib 或一个类或连接一个原型 故事板中的单元格 *** 第一次抛出调用堆栈:( 0 CoreFoundation 0x00007fff23e39f0e exceptionPreprocess + 350 1 libobjc.A.dylib 0x00007fff50ad79b2 objc_exception_throw + 48 2 核心基础 0x00007fff23e39c88 +[NSException raise:format:arguments:] + 88 3 基础 0x00007fff258a3cd2 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 191 4 UIKitCore 0x00007fff4838b36e -[UICollectionView _dequeueReusableViewOfKind:withIdentifier:forIndexPath:viewCategory:] + 2426 5 UIKitCore 0x00007fff4838b53d -[UICollectionView dequeueReusableCellWithReuseIdentifier:forIndexPath:] + 88 6 SwiftUICollectionView 0x0000000108b5fcb3 $s21SwiftUICollectionView010CollectionC10ControllerC12cellProvider33_7499D878310ABE7B5F37FEF32561A438LL010collectionC09indexPath4itemSo0bC4CellCSgSo0bC0C_10Foundation05IndexP0VAA4ItemCtF + 867 7 SwiftUICollectionView 0x0000000108b62ad0 $s21SwiftUICollectionView010CollectionC10ControllerC12cellProvider33_7499D878310ABE7B5F37FEF32561A438LL010collectionC09indexPath4itemSo0bC4CellCSgSo0bC0C_10Foundation05Index + 16 8 SwiftUICollectionView 0x0000000108b5ffaf $sSo16UICollectionViewC10Foundation9IndexPathV05SwiftaB04ItemCSo0aB4CellCSgIeggngo_AbehKIeggnno_TR + 15 9 libswiftUIKit.dylib 0x00007fff5170a8ce $ s5UIKit29UITableViewDiffableDataSourceC05tableC012cellProviderACyxq_GSo0bC0C_So0bC4CellCSgAH_10Foundation9IndexPathVq_tctcfcAkH_ANyptcfU_ + 126 10 libswiftUIKit.dylib 0x00007fff5170a998 $ sSo11UITableViewC10Foundation9IndexPathVypSo0aB4CellCSgIeggnno_ABSo07NSIndexE0CyXlAHIeyByyya_TR + 168 11 UIKitCore 0x00007fff48342a89 -[__UIDiffableDataSource collectionView:cellForItemAtIndexPath:] + 165 12 UIKitCore 0x00007fff483432d5 -[__UIDiffableDataSource _cellForItemAtIndexPath:collectionView:] + 50 13 libswiftUIKit.dylib 0x00007fff5170b036 $s5UIKit34UICollectionViewDiffableDataSourceC010collectionC0_13cellForItemAtSo0bC4CellCSo0bC0C_10Foundation9IndexPathVtFTm + 70 14 libswiftUIKit.dylib 0x00007fff5170b110 $s5UIKit34UICollectionViewDiffableDataSourceC010collectionC0_13cellForItemAtSo0bC4CellCSo0bC0C_10Foundation9IndexPathVtFToTm + 128 15 UIKitCore 0x00007fff483759d5 -[UICollectionView _createPreparedCellForItemAtIndexPath:withLayoutAttributes:applyAttributes:isFocused:notify:] + 416 16 UIKitCore 0x00007fff4837582f -[UICollectionView _createPreparedCellForItemAtIndexPath:withLayoutAttributes:applyAttributes:] + 31 17 UIKitCore 0x00007fff48395e4e __51-[UICollectionView _viewAnimationsForCurrentUpdate]_block_invoke.1814 + 661 18 UIKitCore 0x00007fff483929e4 -[UICollectionView _viewAnimationsForCurrentUpdate] + 3213 19 UIKitCore 0x00007fff48399cde __71-[UICollectionView _updateWithItems:tentativelyForReordering:animator:]_block_invoke.1887 + 118 20 UIKitCore 0x00007fff490bb8a8 +[UIView(Animation) performWithoutAnimation:] + 84 21 UIKitCore 0x00007fff48398b24 -[UICollectionView _updateWithItems:tentativelyForReordering:animator:] + 4003 22 UIKitCore 0x00007fff48391689 -[UICollectionView _endItemAnimationsWithInvalidationContext:tentativelyForReordering:animator:] + 16761 23 UIKitCore 0x00007fff4839b0e6 -[UICollectionView _endUpdatesWithInvalidationContext:tentativelyForReordering:animator:] + 71 24 UIKitCore 0x00007fff4839b445 -[UICollectionView _performBatchUpdates:completion:invalidationContext:tentativelyForReordering:animator: + 462 25 UIKitCore 0x00007fff4839b254 -[UICollectionView _performBatchUpdates:completion:invalidationContext:tentativelyForReordering:] + 90 26 UIKitCore 0x00007fff4839b1d7 -[UICollectionView _performBatchUpdates:completion:invalidationContext:] + 74 27 UIKitCore 0x00007fff4839b12c -[UICollectionView performBatchUpdates:completion:] + 53 28 UIKitCore 0x00007fff483aa9ef -[UICollectionView _performDiffableUpdate:] + 44 29 UIKitCore 0x00007fff4834906e -[_UIDiffableDataSourceViewUpdater _performUpdateWithCollectionViewUpdateItems:dataSourceSnapshotter:updateHandler:completion:] + 467 30 UIKitCore 0x00007fff4834213a -[__UIDiffableDataSource _commitNewDataSource:withViewUpdates:completion:] + 246 31 UIKitCore 0x00007fff4833cb22 __66-[__UIDiffableDataSource applyDifferencesFromSnapshot:completion:]_block_invoke.154 + 190 32 UIKitCore 0x00007fff4833cdb1 __66-[__UIDiffableDataSource applyDifferencesFromSnapshot:completion:]_block_invoke.180 + 107 33 libdispatch.dylib 0x0000000108e01e8e _dispatch_client_callout + 8 34 libdispatch.dylib 0x0000000108e10ae2 _dispatch_lane_barrier_sync_invoke_and_complete + 132 35 UIKitCore 0x00007fff4833c62b -[__UIDiffableDataSource applyDifferencesFromSnapshot:completion:] + 952 36 UIKitCore 0x00007fff4833d63d -[__UIDiffableDataSource applyDifferencesFromSnapshot:animatingDifferences:completion:] + 71 37 libswiftUIKit.dylib 0x00007fff5170aaf4 $s5UIKit34UICollectionViewDiffableDataSourceC5apply_20animatingDifferences10completionyAA010NSDiffableeF8SnapshotVyxq_G_SbyycSgtFTm + 212 38 SwiftUICollectionView 0x0000000108b620b9 $s21SwiftUICollectionView010CollectionC10ControllerC16reloadDataSource8snapshot9animatingy5UIKit010NSDiffablegH8SnapshotVyAA7SectionOAA4ItemCG_SbtF + 985 39 SwiftUICollectionView 0x0000000108b5682f $s21SwiftUICollectionView010CollectionC0V22updateUIViewController_7contextyAA0dcG0C_0A2UI0fG20RepresentableContextVyACGtF + 335 40 SwiftUICollectionView 0x0000000108b56a2b $s21SwiftUICollectionView010CollectionC0V0A2UI29UIViewControllerRepresentableAadEP06updatefG0_7contexty0fG4TypeQz_AD0fgH7ContextVyxGtFTW + 59 41 SwiftUI 0x00007fff2c59a1b2 $s7SwiftUI42PlatformViewControllerRepresentableAdaptorV06updateD8Provider_7contexty06UIViewE4TypeQz_AA0cdF7ContextVyACyxGGtF + 290 42 SwiftUI 0x00007fff2c670439 $s7SwiftUI17PlatformViewChild33_A513612C07DFA438E70B9FA90719B40DLLV6update7contexty14AttributeGraph0O7ContextVyADyxGGz_tFyyXEfU_yyXEfU_ + 217 43 SwiftUI 0x00007fff2c7f5e20 $s7SwiftUI16ViewRendererHostPAAE21performExternalUpdateyyyyXEF + 192 44 SwiftUI 0x00007fff2c66f810 $s7SwiftUI17PlatformViewChild33_A513612C07DFA438E70B9FA90719B40DLLV6update7contexty14AttributeGraph0O7ContextVyADyxGGz_tFyyXEfU_7performL_4workyyyXE_tAA0cD13RepresentableRzlF + 224 45 SwiftUI 0x00007fff2c66e6b6 $s7SwiftUI17PlatformViewChild33_A513612C07DFA438E70B9FA90719B40DLLV6update7contexty14AttributeGraph0O7ContextVyADyxGGz_tFyyXEfU_ + 2454 46 SwiftUI 0x00007fff2c6674ae $s7SwiftUI17PlatformViewChild33_A513612C07DFA438E70B9FA90719B40DLLV6update7contexty14AttributeGraph0O7ContextVyADyxGGz_tF + 590 47 SwiftUI 0x00007fff2c670940 $s7SwiftUI17PlatformViewChild33_A513612C07DFA438E70B9FA90719B40DLLVyxG14AttributeGraph07UntypedM0AafGP7_update_5graph9attributeySv_So10AGGraphRefaSo11AGAttributeatFZTW + 32 48 属性图 0x00007fff2fc46309 $sTA + 25 49 属性图 0x00007fff2fc2ed45 _ZN2AG5Graph11UpdateStack6updateEv + 455 50 属性图 0x00007fff2fc2f253 _ZN2AG5Graph16update_attributeEjb + 373 属性图 0x00007fff2fc33d5b _ZN2AG8Subgraph6updateEj + 729 52 SwiftUI 0x00007fff2c51d690 $s7SwiftUI9ViewGraphC14runTransaction33_D63C4EB7F2B205694B6515509E76E98BLL2inySo10AGGraphRefa_tF + 224 53 SwiftUI 0x00007fff2c51da67 $s7SwiftUI9ViewGraphC13updateOutputs2atyAA4TimeV_tFSb5prefs_Sb9idealSizeAC0F0V7outputstSo10AGGraphRefaXEfU_ + 103 54 SwiftUI 0x00007fff2c51d74d $s7SwiftUI9ViewGraphC13updateOutputs2atyAA4TimeV_tF + 125 55 SwiftUI 0x00007fff2c80146b $s7SwiftUI16ViewRendererHostPAAE6render8interval17updateDisplayListySd_SbtFyyXEfU_yyXEfU_ + 811 56 SwiftUI 0x00007fff2c8008c3 $s7SwiftUI16ViewRendererHostPAAE6render8interval17updateDisplayListySd_SbtFyyXEfU_ + 547 57 SwiftUI 0x00007fff2c7f6415 $s7SwiftUI16ViewRendererHostPAAE6render8interval17updateDisplayListySd_SbtF + 373 58 SwiftUI 0x00007fff2c956102 $s7SwiftUI14_UIHostingViewC14layoutSubviewsyyF + 226 59 SwiftUI 0x00007fff2c956125 $s7SwiftUI14_UIHostingViewC14layoutSubviewsyyFTo + 21 60 UIKitCore 0x00007fff490c9848 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 2478 61 QuartzCore 0x00007fff2b4ae3f0 -[CALayer layoutSublayers] + 255 62 QuartzCore 0x00007fff2b4b457b _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 523 63 石英核心 0x00007fff2b4bfc12 _ZN2CA5Layer28layout_and_display_if_neededEPNS_11TransactionE + 80 64 QuartzCore 0x00007fff2b408c84 _ZN2CA7Context18commit_transactionEPNS_11TransactionEd + 324 65 QuartzCore 0x00007fff2b43c65f _ZN2CA11Transaction6commitEv + 649 66 UIKitCore 0x00007fff48bdfc2b __34-[UIApplication _firstCommitBlock]_block_invoke_2 + 81 67 CoreFoundation 0x00007fff23d9dcdc __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK + 12 68 核心基础 0x00007fff23d9d3d3 __CFRunLoopDoBlocks + 195 69 CoreFoundation 0x00007fff23d981c3 __CFRunLoopRun + 995 70 CoreFoundation 0x00007fff23d97ac4 CFRunLoopRunSpecific + 404 71 图形服务 0x00007fff38b2fc1a GSEventRunModal + 139 72 UIKitCore 0x00007fff48bc7f80 UIApplicationMain + 1605 73 SwiftUICollectionView 0x0000000108b548fb 主 + 75 74 libdyld.dylib 0x00007fff519521fd 开始 + 1)

我忘了说,当我用简单的 UICollectionFlowLayout 替换 CompositionalLayout 时,就会开始出现这种异常!

【问题讨论】:

【参考方案1】:

正如我所见,您没有在 updateUIViewController 方法中应用新快照。应该是这样的

func updateUIViewController(_ controller: CollectionViewController, context: Context) 

    controller.snapshot = snapshotForCurrentState()
    controller.reloadDataSource()

【讨论】:

我有这段代码 func updateUIViewController(_ controller: CollectionViewController, context: Context) controller.snapshot = snapshotForCurrentState() controller.reloadDataSource() 上面我只是粘贴了错误的代码。但是用这个 reloadDataSource() 它不起作用

以上是关于SwiftUI CollectionView 包装器无法从 updateUIViewController 应用快照的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI iOS13情况下实现类似于CollectionView的效果

SwiftUI iOS13情况下实现类似于CollectionView的效果

SwiftUI iOS13情况下实现类似于CollectionView的效果

SwiftUI iOS13情况下实现类似于CollectionView的效果

SwiftUI 用图像包装 HStack

在 SwiftUI 中使用 @State 属性包装器未更新视图