Kingfisher icon indicating copy to clipboard operation
Kingfisher copied to clipboard

Crash when using a resizable KFImage inside a List

Open ir-fuel opened this issue 3 years ago • 7 comments

Check List

Thanks for considering to open an issue. Before you submit your issue, please confirm these boxes are checked.

Issue Description

When using the KFImage class inside a SwiftUI List there is a crash, when applying resizing. This does not happen without those modifiers.

What

The following error is reported in Xcode

2021-03-22 08:53:55.771399+0100 XXXX[52630:6401068] [Assert] Attempted to call -cellForRowAtIndexPath: on the table view while it was in the process of updating its visible cells, which is not allowed. Make a symbolic breakpoint at UITableViewAlertForCellForRowAtIndexPathAccessDuringUpdate to catch this in the debugger and see what caused this to occur. Perhaps you are trying to ask the table view for a cell from inside a table view callback about a specific row? Table view: <_TtC7SwiftUIP33_BFB370BA5F1BADDC9D83021565761A4925UpdateCoalescingTableView: 0x7feda381ea00; baseClass = UITableView; frame = (0 0; 414 896); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x600002bfa8b0>; layer = <CALayer: 0x600002402e40>; contentOffset: {0, -144}; contentSize: {414, 561}; adjustedContentInset: {144, 0, 34, 0}; dataSource: <_TtGC7SwiftUIP13$7fff57aa482819ListCoreCoordinatorGVS_20SystemListDataSourceOs5Never_GOS_19SelectionManagerBoxS2___: 0x7fedb352be30>>

Reproduce

The following SwiftUI code works

    var body: some View {
        Group {
            ZStack(alignment: .bottom) {
                List {
                    if viewModel.pictureURL != nil {
                        Section {
                            KFImage(viewModel.pictureURL!)
                        }
                    }
            }
        }
    }

The following code crashes

    var body: some View {
        Group {
            ZStack(alignment: .bottom) {
                List {
                    if viewModel.pictureURL != nil {
                        Section {
                            KFImage(viewModel.pictureURL!)
                                .resizable()
                                .frame(height:128)
                        }
                    }
            }
        }
    }

ir-fuel avatar Mar 22 '21 07:03 ir-fuel

I am not experiencing a crash with exact your code snippet (with Xcode 12.2 and iOS 14.4 simulator). Is there any condition or only crashes on a certain Xcode / iOS version? Or do you have some more completed code sample for the crash?

onevcat avatar Mar 22 '21 08:03 onevcat

I've just been asked to finalise another project. I will come back to this later. Maybe it's indirectly related to something else in that View. I'll put some parts in comments until I find where it comes from.

ir-fuel avatar Mar 22 '21 08:03 ir-fuel

Narrowed it down to this.

This crashes:

    var body: some View {
        List {
            KFImage(URL(string: "https://img-19.ccm2.net/ppaPB1I48R0LInb9Z8QBoUqXqSQ=/480x335/smart/b829396acc244fd484c5ddcdcb2b08f3/ccmcms-commentcamarche/20494859.jpg")!)
            .resizable()
            }
    }

This doesn't

    var body: some View {
        List {
            KFImage(URL(string: "https://img-19.ccm2.net/ppaPB1I48R0LInb9Z8QBoUqXqSQ=/480x335/smart/b829396acc244fd484c5ddcdcb2b08f3/ccmcms-commentcamarche/20494859.jpg")!)
            }
    }

And it clearly crashes after downloading the image (the error message in my first post here is unrelated, it seems).

Error is Thread 1: EXC_BAD_ACCESS (code=1, address=0xfffffffffffffff8)

call stack

#0	0x00007fff4cd87c54 in AG::AttributeID::size() const ()
#1	0x00007fff4cd7c8d9 in AG::Graph::add_indirect_attribute(AG::Subgraph&, AG::AttributeID, unsigned long, std::__1::optional<unsigned long>, bool) ()
#2	0x00007fff4cd8e64b in (anonymous namespace)::create_indirect_attribute(unsigned int, std::__1::optional<unsigned long>) ()
#3	0x00007fff57905844 in partial apply for thunk for @callee_guaranteed () -> (@unowned IndirectAttribute<A>) ()
#4	0x00007fff579056c8 in closure #1 in AGSubgraphRef.apply<A>(_:) ()
#5	0x00007fff579054f7 in Attribute.makeReusable(indirectMap:) ()
#6	0x00007fff5790212b in closure #1 in closure #1 in closure #1 in ModifiedElements.makeElements(from:inputs:indirectMap:body:) ()
#7	0x00007fff579075d5 in partial apply for closure #1 in closure #1 in closure #1 in ModifiedElements.makeElements(from:inputs:indirectMap:body:) ()
#8	0x00007fff575fd7cb in closure #1 in closure #1 in PlaceholderInfo.makeItem(placeholder:seed:) ()
#9	0x00007fff575feb1d in partial apply for closure #1 in closure #1 in PlaceholderInfo.makeItem(placeholder:seed:) ()
#10	0x00007fff5772c1ac in thunk for @callee_guaranteed (@in_guaranteed _ViewInputs, @guaranteed @escaping @callee_guaranteed (@in_guaranteed _ViewInputs) -> (@out _ViewOutputs)) -> (@out _ViewOutputs?) ()
#11	0x00007fff575feb61 in partial apply for thunk for @callee_guaranteed (@in_guaranteed _ViewInputs, @guaranteed @escaping @callee_guaranteed (@in_guaranteed _ViewInputs) -> (@out _ViewOutputs)) -> (@out _ViewOutputs?) ()
#12	0x00007fff578fd0f3 in closure #1 in closure #1 in _ViewList_Elements.makeOneElement(at:inputs:indirectMap:body:) ()
#13	0x00007fff57907531 in partial apply for thunk for @callee_guaranteed (@in_guaranteed _ViewInputs, @guaranteed @escaping @callee_guaranteed (@in_guaranteed _ViewInputs) -> (@out _ViewOutputs)) -> (@out _ViewOutputs?, @unowned Bool) ()
#14	0x00007fff57901f70 in closure #1 in closure #1 in ModifiedElements.makeElements(from:inputs:indirectMap:body:) ()
#15	0x00007fff5790756a in partial apply for closure #1 in closure #1 in ModifiedElements.makeElements(from:inputs:indirectMap:body:) ()
#16	0x00007fff578ffb92 in UnaryElements.makeElements(from:inputs:indirectMap:body:) ()
#17	0x00007fff57901e5e in closure #1 in ModifiedElements.makeElements(from:inputs:indirectMap:body:) ()
#18	0x00007fff57901d47 in ModifiedElements.makeElements(from:inputs:indirectMap:body:) ()
#19	0x00007fff5790362f in SubgraphElements.makeElements(from:inputs:indirectMap:body:) ()
#20	0x00007fff5790362f in SubgraphElements.makeElements(from:inputs:indirectMap:body:) ()
#21	0x00007fff578fd06b in closure #1 in _ViewList_Elements.makeOneElement(at:inputs:indirectMap:body:) ()
#22	0x00007fff575fd23b in closure #1 in PlaceholderInfo.makeItem(placeholder:seed:) ()
#23	0x00007fff575fcca9 in PlaceholderInfo.updateValue() ()
#24	0x00007fff572d823f in partial apply for specialized implicit closure #2 in implicit closure #1 in closure #1 in closure #1 in Attribute.init<A>(_:) ()
#25	0x00007fff4cd78723 in AG::Graph::UpdateStack::update() ()
#26	0x00007fff4cd78bb9 in AG::Graph::update_attribute(AG::data::ptr<AG::Node>, bool) ()
#27	0x00007fff4cd80b91 in AG::Subgraph::update(unsigned int) ()
#28	0x00007fff5798559a in GraphHost.runTransaction() ()
#29	0x00007fff57458cba in ViewGraph.updateOutputs(at:) ()
#30	0x00007fff573f04e4 in specialized closure #1 in ViewRendererHost.render(interval:updateDisplayList:) ()
#31	0x00007fff573ed28e in specialized ViewRendererHost.render(interval:updateDisplayList:) ()
#32	0x00007fff573ea462 in specialized _UIHostingView.updatePreferences() ()
#33	0x00007fff573a215e in CellForRowVisitor.visit(view:traits:) ()
#34	0x00007fff57759089 in specialized closure #1 in closure #1 in _ViewList_Backing.visitViews<A>(applying:from:) ()
#35	0x00007fff578fc494 in _ViewList_Node.applySublists(from:style:transform:to:) ()
#36	0x00007fff57905900 in partial apply for closure #1 in _ViewList_Node.applySublists(from:style:transform:to:) ()
#37	0x00007fff578ff6ca in BaseViewList.applyNodes(from:style:list:transform:to:) ()
#38	0x00007fff57902ab1 in ModifiedViewList.applyNodes(from:style:list:transform:to:) ()
#39	0x00007fff5764dd3a in AnyViewList.WrappedList.applyNodes(from:style:list:transform:to:) ()
#40	0x00007fff5782f7ac in IDViewList.WrappedList.applyNodes(from:style:list:transform:to:) ()
#41	0x00007fff578fc534 in ViewList.applySublists(from:style:list:transform:to:) ()
#42	0x00007fff578fa833 in ViewList.applySublists(from:style:list:to:) ()
#43	0x00007fff574ea8a7 in SystemListDataSource.configureCell(_:transaction:forRowAt:) ()
#44	0x00007fff576b07ef in ShadowListDataSource.configureCell(_:transaction:forRowAt:) ()
#45	0x00007fff578e916c in ListCoreDataSource.configureCell(_:transaction:forRowAt:) ()
#46	0x00007fff5758e115 in ListCoreCoordinator.updateListContents(_:) ()
#47	0x00007fff5758d80f in closure #1 in closure #1 in closure #2 in ListCoreCoordinator.updateUITableView(_:to:transaction:) ()
#48	0x00007fff5758d7ba in closure #1 in closure #2 in ListCoreCoordinator.updateUITableView(_:to:transaction:) ()
#49	0x00007fff57a4ed3e in thunk for @escaping @callee_guaranteed () -> () ()
#50	0x00007fff24b9107a in -[_UIAfterCACommitBlock run] ()
#51	0x00007fff246a5954 in _runAfterCACommitDeferredBlocks ()
#52	0x00007fff246959fc in _cleanUpAfterCAFlushAndRunDeferredBlocks ()
#53	0x00007fff246c72ac in _afterCACommitHandler ()
#54	0x00007fff2038f1f8 in __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ ()
#55	0x00007fff20389a77 in __CFRunLoopDoObservers ()
#56	0x00007fff2038a01a in __CFRunLoopRun ()
#57	0x00007fff203896d6 in CFRunLoopRunSpecific ()
#58	0x00007fff2c257db3 in GSEventRunModal ()
#59	0x00007fff24696cf7 in -[UIApplication _run] ()
#60	0x00007fff2469bba8 in UIApplicationMain ()
#61	0x000000010d252cab in main at /Users/Joris/Development/Sweel/iOS/Sweel/AppDelegate.swift:14
#62	0x00007fff2025a3e9 in start ()
#63	0x00007fff2025a3e9 in start ()

ir-fuel avatar Mar 22 '21 13:03 ir-fuel

The crash indeed is in a table view (List) when rendering something. But it is deeply in SwiftUI.

And again, I cannot reproduce this by the simple snippet. See the screenshot below:

2021-03-23 09 39 02

Can you try to create an empty project and try the code there to see if it is triggering the crash?

If you can get the same result as mine, so there is must something other triggers this crash. I need more context to track it down.

onevcat avatar Mar 23 '21 00:03 onevcat

Hey, just wanted to share some observations I noticed when this issue cropped up for me.

  • This only seems to show up on a physical device, I couldn't get the crash to occur on simulator.
  • The KFImage had to be inside a NavigationLink.
  • The NavigationLink had to be inside a List
  • The final trigger (for me) was using a placeholder view. The transition from placeholder -> image changes the size of the view and causes the crash.
    • It could be that .resizable() also triggers this in other scenarios without the placeholder, but I didn't test that.
    • Once the images were cached, the placeholder wasn't used and crash didn't occur.
    • In my test, I used 3 different image urls and enabled memory-only caching so the transition would always occur.

When those conditions were met ( physical device, List of NavigationLink displaying a KFImage using placeholder), the crash occurred each time.

Like you mentioned, the crash seems to be rather deep in SwiftUI, which seems tricky. Hopefully having reproduction steps will help resolve if possible.

Here's the view I tested with in a new project, please let me know if you have other questions about replication steps. Device is a 6th gen iPad running iOS 14.0.1.

import SwiftUI
import Kingfisher

struct ContentView: View {
    let urls = [
        URL(string: "https://image.shutterstock.com/image-illustration/color-splash-series-background-design-600w-587409425.jpg")!,
        URL(string: "https://image.shutterstock.com/image-photo/smart-city-abstract-dot-point-600w-1499306735.jpg")!,
        URL(string: "https://image.shutterstock.com/image-illustration/bright-artistic-splashes-abstract-painting-600w-717161425.jpg")!
    ]
    
    var body: some View {
        NavigationView {
            List(0..<25) { i in
                NavigationLink(destination: Elsewhere()) {
                    KFImage
                        .url(urls[i%3])
                        .cacheMemoryOnly(true)
                        .placeholder { placeholderView }
                }
            }
        }
        .navigationViewStyle(StackNavigationViewStyle())
    }
    
    private var placeholderView: some View {
        Circle().background(Color.green)
    }
}

struct Elsewhere: View {
    var body: some View {
        EmptyView()
    }
}

wyattbeavers avatar May 04 '21 00:05 wyattbeavers

For anyone else encountering this, I've had some success working around it for now by splitting up the KFImage and NavigationLink.

@State private var selectedThing: Thing? = nil

var body: some View {
    List(listOfThings) { thing in
        VStack {
            row(for: thing) // some view representing the item in the List, including the KFImage
            navigationLink(for: thing) // a NavigationLink to the item, with an EmptyView() label
        }
    }
}

private func row(for thing: Thing) -> some View {
    KFImage.url(thing.url)
    // whatever else
}

private func navigationLink(for thing: Thing) -> some View {
    NavigationLink(
        destination: DestinationView(),
        tag: thing,
        selection: $selectedThing) {
        EmptyView()
    }
}
    

wyattbeavers avatar May 05 '21 21:05 wyattbeavers

@wyattbeavers Thanks for your investigation. That's really weird and I guess there is quite little I can do with it.

I tried your sample code snippet on my iPhone with 14.5.1 but cannot reproduce it. I will try to find more devices now and see if there is any luck I can see this crash.

Anyway, good to hear that you have a workaround for it now.

onevcat avatar May 07 '21 13:05 onevcat