layoutBox/PinLayout

Crash: PinLayout.swift - Line 1236

orojasnypost opened this issue · 7 comments

Firebase crashlytics is reporting that our app is crashing after PinLayout tries to call referenceSuperview and this method calls viewDescription. It seems that the object referenceView which should be created from anchor.view in the method computeCoordinatesForAnchors in the line 384 of PinLayout+Coordinates is nil. And this causes referenceSuperview to fail to create a reference to the superview from referenceView.superview. Because of this when warnWontBeApplied is called in line 1236 of PinLayout crashes when it tries to call viewDescription(referenceView)

This is the stacktrace from crashlytics:

Crashed: com.apple.main-thread
0  libswiftCore.dylib             0x3da990 swift_getObjectType + 40
1  PinLayout                      0x2557c PinLayout.viewDescription(_:) + 4375287164 (<compiler-generated>:4375287164)
2  PinLayout                      0xb684 PinLayout.referenceSuperview(_:_:) + 1236 (PinLayout.swift:1236)
3  PinLayout                      0x101b4 closure #1 in PinLayout.computeCoordinates(forAnchors:_:) + 384 (PinLayout+Coordinates.swift:384)
4  PinLayout                      0xfdb8 PinLayout.computeCoordinates(forAnchors:_:) + 4375199160 (<compiler-generated>:4375199160)
5  PinLayout                      0x1987c PinLayout.below(of:aligned:context:) + 170 (PinLayout+Relative.swift:170)
6  PinLayout                      0x19e30 PinLayout.above(of:aligned:) + 4375240240
7  NYP                            0x2b9fd4 NYPVerticalContainerFrameView.layout() + 92 (NYPVerticalContainerFrameView.swift:92)
8  NYP                            0x2ba9a4 protocol witness for FrameViewProtocol.layout() in conformance NYPVerticalContainerFrameView + 4334070180 (<compiler-generated>:4334070180)
9  NYP                            0x6f7e48 FrameViewProtocol<>.heightThatFits(_:) + 43 (FrameViewType.swift:43)
10 NYP                            0x26b3ac NYPVerticalContainerFrame.height(forWidth:) + 91 (NYPVerticalContainerFrame.swift:91)
11 NYP                            0x654360 ContainerCollectionViewLayout.attributesFor(frame:currentXOffset:currentYOffset:indexPath:inRow:) + 47 (Container+LayoutHelpers.swift:47)
12 NYP                            0x65b068 calculateAttributes #1 (for:) in attemptToLayout #1 (frame:atIndex:) in ContainerCollectionViewLayout.prepare() + 447 (ContainerCollectionViewLayout.swift:447)
13 NYP                            0x65a1d4 attemptToLayout #1 (frame:atIndex:) in ContainerCollectionViewLayout.prepare() + 472 (ContainerCollectionViewLayout.swift:472)
14 NYP                            0x655ec0 ContainerCollectionViewLayout.prepare() + 498 (ContainerCollectionViewLayout.swift:498)
15 NYP                            0x65bee8 @objc ContainerCollectionViewLayout.prepare() + 4337876712 (<compiler-generated>:4337876712)
16 UIKitCore                      0x174154 -[UICollectionViewData _prepareToLoadData] + 200
17 UIKitCore                      0x176b4c -[UICollectionViewData validateLayoutInRect:] + 96
18 UIKitCore                      0x179be8 -[UICollectionView layoutSubviews] + 220
19 UIKitCore                      0x18c17c -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 2592
20 QuartzCore                     0x407fc CA::Layer::layout_if_needed(CA::Transaction*) + 532
21 QuartzCore                     0x32c60 CA::Layer::layout_and_display_if_needed(CA::Transaction*) + 136
22 QuartzCore                     0x475b4 CA::Context::commit_transaction(CA::Transaction*, double, double*) + 452
23 QuartzCore                     0x504a8 CA::Transaction::commit() + 704
24 QuartzCore                     0x323a0 CA::Transaction::flush_as_runloop_observer(bool) + 88
25 UIKitCore                      0x53e6e0 _UIApplicationFlushCATransaction + 72
26 UIKitCore                      0x7d8d5c _UIUpdateSequenceRun + 84
27 UIKitCore                      0xe5fedc schedulerStepScheduledMainSection + 144
28 UIKitCore                      0xe5f6a4 runloopSourceCallback + 92
29 CoreFoundation                 0xbb414 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 28
30 CoreFoundation                 0xcc1a0 __CFRunLoopDoSource0 + 208
31 CoreFoundation                 0x5694 __CFRunLoopDoSources0 + 268
32 CoreFoundation                 0xb05c __CFRunLoopRun + 828
33 CoreFoundation                 0x1ebc8 CFRunLoopRunSpecific + 600
34 GraphicsServices               0x1374 GSEventRunModal + 164
35 UIKitCore                      0x514b58 -[UIApplication _run] + 1100
36 UIKitCore                      0x296090 UIApplicationMain + 364
37 NYP                            0x66f8 main + 24 (NYPMediaCoder.swift:24)
38 ???                            0x10392dda4 (Missing)

I'm trying to figure out what is causing the issue.

We are doing something like this:

var margin = viewModel.margins
var height: CGFloat = 0.0

for (n, uiView) in frameView.enumerated() { 
   if n == 0 {
        uiView.pin.top(margin).horizontally(margin).height(height)
   } else {                  
        let previousView = frameView[n - 1]
   if n == frameView.count - 1 {
        uiView.pin.below(of: previousView).bottom(margin).horizontally(margin).height(height)
   } else {
        uiView.pin.below(of: previousView).horizontally(margin).height(height)
           }
        }
     }

According to the data from the stacktrace: NYPVerticalContainerFrameView.layout() + 92
Is the line if n == frameView.count - 1 { ... but I think the crash is being caused by line 93 which is:

uiView.pin.below(of: previousView).bottom(margin).horizontally(margin).height(height)

Any insights into this?

@orojasnypost, the method referenceSuperview() is trying to show you a warning that the view don't have a superview, which means that the view you are trying to layout has not been added yet to another view, so PinLayout is unable to layout it.

I can't explain why it crashes in viewDescription, but adding the view you are layouting to a superview should fix your issue.

internal func referenceSuperview(_ referenceView: PinView, _ context: Context) -> PinView? {
        if let superview = referenceView.superview {
            return superview as? PinView
        } else {
            warnWontBeApplied("the reference view \(viewDescription(referenceView)) must be added as a sub-view before being used as a reference.", context)
            return nil
        }
    }

Also, normally PinLayout's warnings shouldn't be generated in release builds because Pin.logWarnings is false in release mode. Are you testing a DEBUG build?

internal func warnWontBeApplied(_ text: String, _ context: Context) {
        guard Pin.logWarnings else { return }
        warn("\(context()) won't be applied, \(text)")
    }

@orojasnypost, the method referenceSuperview() is trying to show you a warning that the view don't have a superview, which means that the view you are trying to layout has not been added yet to another view, so PinLayout is unable to layout it.

I can't explain why it crashes in viewDescription, but adding the view you are layouting to a superview should fix your issue.

internal func referenceSuperview(_ referenceView: PinView, _ context: Context) -> PinView? {
        if let superview = referenceView.superview {
            return superview as? PinView
        } else {
            warnWontBeApplied("the reference view \(viewDescription(referenceView)) must be added as a sub-view before being used as a reference.", context)
            return nil
        }
    }

We are adding the view we are layouting to the superView, all subviews are added to the superview before layouting. That's why I reached out for solutions and created this issue, because I don't have an idea of why is the referenceView nil but something is happening inside PinLayout IMHO because we aren't doing anything unusual, we use pinLayout regularly in all our views but this is the one that is reporting a problem in firebase.

Also, normally PinLayout's warnings shouldn't be generated in release builds because Pin.logWarnings is false in release mode. Are you testing a DEBUG build?

internal func warnWontBeApplied(_ text: String, _ context: Context) {
        guard Pin.logWarnings else { return }
        warn("\(context()) won't be applied, \(text)")
    }

Crashlytics is only generating a crash report with this title:

<compiler-generated> line 2147483647

If you check the stack trace I shared above you'll see that it reports the crash here in frame 1:

Crashed: com.apple.main-thread
0 libswiftCore.dylib 0x3da990 swift_getObjectType + 40
1 PinLayout 0x2557c PinLayout.viewDescription(_:) + 4375287164 (:4375287164)

I came to the conclusion that the crash is ocurring there after debugging with breakpoints. But we aren't receiving any warning, this is in production code. Not a Debug build.

  • If you are able to reproduce the issue, you could check in your loop for (n, uiView) in frameView.enumerated() that all your view's superview are valid.

  • Check that all views layout and update are done on the main thread.

  • Check you don't set Pin.logWarnings to true anywhere on your code.

We have never encountered this issue before, its really strange 🤔

Thanks for your help.

  1. All views and layout updating is done in the main thread.
  2. Pin.logWarnings = true is nowhere in our code
  3. I tried validating that the superView of the previous view isn't nil before pinning it. Now we'll have to wait until the fix is released and see if it fixes the crash. Thank you!

Good, for now I will close the issue, if the issue persist you could reopen it later