ephread/Instructions

Crash - NSLayoutConstraint constant is not finite!

haffez23 opened this issue · 6 comments

I logged a crash in my firebase console, it happned for one user 3 times and I cant reproduce it 
Here

Environment

  • Device: iPhone 13 Pro
  • iOS version: 16
  • Instructions version: main
  • Dependency Manager: CocoaPods

Trace of crash:
Fatal Exception: NSInternalInconsistencyException
NSLayoutConstraint constant is not finite! That's illegal. constant:inf firstAnchor:<NSLayoutYAxisAnchor:0x283d7fd80 "Instructions.CoachMarkView:0x11ffb0050.bottom"> secondAnchor:<NSLayoutYAxisAnchor:0x283db3840
"Instructions.InstructionsRootView:0x11ffa7ab0.bottom">



Fatal Exception: NSInternalInconsistencyException
0 CoreFoundation 0xa248 __exceptionPreprocess
1 libobjc.A.dylib 0x17a68 objc_exception_throw
2 Foundation 0x54681c _userInfoForFileAndLine
3 CoreAutoLayout 0xb8e4 -[NSLayoutConstraint _setSymbolicConstant:constant:symbolicConstantMultiplier:]
4 CoreAutoLayout 0x1574 -[NSLayoutConstraint setConstant:]
5 CoreAutoLayout 0x1324 +[NSLayoutConstraint constraintWithAnchor:relatedBy:toAnchor:multiplier:constant:]
6 Instructions 0xfa80 (Manquant UUID e5aa90ad73623213b26ec3ed2d547935)
7 Instructions 0xe868 (Manquant UUID e5aa90ad73623213b26ec3ed2d547935)
8 Instructions 0x16b84 (Manquant UUID e5aa90ad73623213b26ec3ed2d547935)
9 Instructions 0x19f44 (Manquant UUID e5aa90ad73623213b26ec3ed2d547935)
10 Instructions 0x192a0 (Manquant UUID e5aa90ad73623213b26ec3ed2d547935)
11 Instructions 0x166e8 (Manquant UUID e5aa90ad73623213b26ec3ed2d547935)
12 Instructions 0x20bc8 (Manquant UUID e5aa90ad73623213b26ec3ed2d547935)
13 Instructions 0x204e0 (Manquant UUID e5aa90ad73623213b26ec3ed2d547935)
14 UIKitCore 0x10251b0 UIVIEW_IS_EXECUTING_ANIMATION_COMPLETION_BLOCK
15 UIKitCore 0xd2114 -[UIViewAnimationBlockDelegate _didEndBlockAnimation:finished:context:]
16 UIKitCore 0xd1070 -[UIViewAnimationState sendDelegateAnimationDidStop:finished:]
17 UIKitCore 0xd0790 -[UIViewAnimationState animationDidStop:finished:]
18 QuartzCore 0x13ae8 CA::Layer::run_animation_callbacks(void*)
19 libdispatch.dylib 0x3fdc dispatch_client_callout
20 libdispatch.dylib 0x127f4 dispatch_main_queue_drain
21 libdispatch.dylib 0x12444 dispatch_main_queue_callback_4CF
22 CoreFoundation 0x9aa08 CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE
23 CoreFoundation 0x7c368 __CFRunLoopRun
24 CoreFoundation 0x811e4 CFRunLoopRunSpecific
25 GraphicsServices 0x1368 GSEventRunModal
26 UIKitCore 0x3a2d88 -[UIApplication run]
27 UIKitCore 0x3a29ec UIApplicationMain
28 libswiftUIKit.dylib 0x352a0 UIApplicationMain(
:
:
:
:)
22 MYAPP 0xdfcc main + 4371423180 (MF.swift:4371423180)
23 ??? 0x1be3bd948 (Manquant)

Happens to my users as well, any ETA on a fix?

Added Symbolic Breakpoint "CGPostError" and captured the stacktrace. Also attaching the crash log downloaded from TestFlight. crashlog.txt

  • thread 1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    • frame 0: 0x00000001d1d1c97c CoreGraphicsCGPostError frame 1: 0x00000001d1c81e20 CoreGraphicsCGFloatValidateWithLog + 148
      frame 2: 0x00000001d1c4bb64 CoreGraphicsCGPathAddLineToPoint + 92 frame 3: 0x00000001d243284c UIKitCore+[UIBezierPath _continuousRoundedRectBezierPath:withRoundedCorners:cornerRadii:segments:smoothPillShapes:] + 1192
      frame 4: 0x00000001d2432344 UIKitCore+[UIBezierPath _continuousRoundedRectBezierPath:withRoundedCorners:cornerRadius:segments:] + 184 frame 5: 0x00000001d2431f14 UIKitCore+[UIBezierPath _roundedRectBezierPath:withRoundedCorners:cornerRadius:segments:legacyCorners:] + 344
      frame 6: 0x000000010347f6fc CLSS QA@nonobjc UIBezierPath.__allocating_init(roundedRect:byRoundingCorners:cornerRadii:) at <compiler-generated>:0 frame 7: 0x000000010347e604 CLSS QACoachMarkHelper.update(coachMark=Instructions.CoachMark @ 0x000000016d348e90, frame=(origin = (x = 0, y = 0), size = (width = 597, height = 54)), pointOfInterest=nil, superview=0x000000010a68f290, cutoutPathMaker=nil, self=0x0000000281721ad0) at CoachMarkHelper.swift:285:30
      frame 8: 0x000000010347e1c4 CLSS QACoachMarkHelper.makeCoachMark(view=0x000000010a68f630, pointOfInterest=nil, cutoutPathMaker=nil, self=0x0000000281721ad0) at CoachMarkHelper.swift:108:9 frame 9: 0x000000010339a3e8 CLSS QAFMCoachMarksManager.coachMarksController(coachMarksController=0x00000002832f1ea0, index=0, self=0x0000000281ee2080) at FMCoachMarksManager.swift:64:53
      frame 10: 0x000000010339c050 CLSS QAprotocol witness for CoachMarksControllerDataSource.coachMarksController(_:coachMarkAt:) in conformance FMCoachMarksManager at <compiler-generated>:0 frame 11: 0x00000001034643c4 CLSS QACoachMarksController.coachMark(index=0, self=0x00000002832f1ea0) at CoachMarksController+Proxy.swift:12:28
      frame 12: 0x0000000103464668 CLSS QAprotocol witness for CoachMarksControllerProxyDataSource.coachMark(at:) in conformance CoachMarksController at <compiler-generated>:0 frame 13: 0x0000000103491164 CLSS QAFlowManager.createAndShowCoachMark(afterResuming=false, change=nothing, self=0x0000000282eb48f0) at FlowManager.swift:204:49
      frame 14: 0x00000001034903ac CLSS QAFlowManager.showNextCoachMark(hidePrevious=true, self=0x0000000282eb48f0) at FlowManager.swift:125:13 frame 15: 0x000000010348f644 CLSS QAclosure 1 in FlowManager.startFlow(self=0x0000000282eb48f0) at FlowManager.swift:66:18
      frame 16: 0x000000010345baf8 CLSS QAclosure 1 in CoachMarksViewController.prepareToShowCoachMarks(_0=true, self=0x00000001148a6400, completion=0x000000010348f654 CLSS QApartial apply forwarder for closure 1 () -> () in Instructions.FlowManager.startFlow(withNumberOfCoachMarks: Swift.Int) -> () at ) at CoachMarksViewController.swift:258:13
      frame 17: 0x000000010348a8c8 CLSS QAclosure 2 in TranslucentOverlayStyleManager.showOverlay(success=true, show=true, self=0x0000000280cb5580, overlay=0x000000010a689330, completion=0x000000010345dddc CLSS QApartial apply forwarder for closure 1 (Swift.Bool) -> () in Instructions.CoachMarksViewController.prepareToShowCoachMarks(() -> ()) -> () at ) at TranslucentOverlayStyleManager.swift:77:23
      frame 18: 0x000000010348298c CLSS QAthunk for @escaping @callee_guaranteed (@unowned Bool) -> () at <compiler-generated>:0 frame 19: 0x00000001d3292464 UIKitCoreUIVIEW_IS_EXECUTING_ANIMATION_COMPLETION_BLOCK + 36
      frame 20: 0x00000001d23264ac UIKitCore-[UIViewAnimationBlockDelegate _didEndBlockAnimation:finished:context:] + 636 frame 21: 0x00000001d2325408 UIKitCore-[UIViewAnimationState sendDelegateAnimationDidStop:finished:] + 436
      frame 22: 0x00000001d2324b28 UIKitCore-[UIViewAnimationState animationDidStop:finished:] + 196 frame 23: 0x00000001d173610c QuartzCoreCA::Layer::run_animation_callbacks(void*) + 232
      frame 24: 0x00000001061ea05c libdispatch.dylib_dispatch_client_callout + 20 frame 25: 0x00000001061fa810 libdispatch.dylib_dispatch_main_queue_drain + 1196
      frame 26: 0x00000001061fa354 libdispatch.dylib_dispatch_main_queue_callback_4CF + 44 frame 27: 0x00000001d011a6d8 CoreFoundationCFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE + 16
      frame 28: 0x00000001d00fc03c CoreFoundation__CFRunLoopRun + 2036 frame 29: 0x00000001d0100ec0 CoreFoundationCFRunLoopRunSpecific + 612
      frame 30: 0x00000002098ab368 GraphicsServicesGSEventRunModal + 164 frame 31: 0x00000001d25f686c UIKitCore-[UIApplication _run] + 888
      frame 32: 0x00000001d25f64d0 UIKitCoreUIApplicationMain + 340 frame 33: 0x0000000102b403fc CLSS QAmain(argc=1, argv=0x000000016d34b6c8) at main.m:16:16
      frame 34: 0x00000001ee912960 dyld`start + 2528

seeing this on an iPhone 11 running 16.2

Fatal Exception: NSInternalInconsistencyException
0  CoreFoundation                 0x9e48 __exceptionPreprocess
1  libobjc.A.dylib                0x178d8 objc_exception_throw
2  Foundation                     0x54594c _userInfoForFileAndLine
3  CoreAutoLayout                 0xb8e4 -[NSLayoutConstraint _setSymbolicConstant:constant:symbolicConstantMultiplier:]
4  CoreAutoLayout                 0x1574 -[NSLayoutConstraint setConstant:]
5  CoreAutoLayout                 0x1324 +[NSLayoutConstraint constraintWithAnchor:relatedBy:toAnchor:multiplier:constant:]
6  Instructions                   0xdd48 specialized CoachMarkDisplayManager.generateAndEnableVerticalConstraints(of:forDisplayIn:usingCoachMark:cutoutPath:andOverlayView:) + 256 (CoachMarkDisplayManager.swift:256)
7  Instructions                   0xcb84 CoachMarkDisplayManager.showNew(coachMarkView:from:at:animated:completion:) (<compiler-generated>)
8  Instructions                   0xae44 CoachMarksViewController.show(coachMark:at:animated:completion:) + 291 (CoachMarksViewController.swift:291)
9  Instructions                   0x1baf4 FlowManager.createAndShowCoachMark(afterResuming:changing:) + 221 (FlowManager.swift:221)
10 Instructions                   0x1ae48 FlowManager.showNextCoachMark(hidePrevious:) + 178 (FlowManager.swift:178)
11 Instructions                   0xa9bc closure #1 in CoachMarksViewController.prepareToShowCoachMarks(_:) + 259 (CoachMarksViewController.swift:259)
12 Instructions                   0x12094 closure #2 in TranslucentOverlayStyleManager.showOverlay(_:withDuration:completion:) + 78 (TranslucentOverlayStyleManager.swift:78)
13 Instructions                   0x1d130 thunk for @escaping @callee_guaranteed (@unowned Bool) -> () (<compiler-generated>)
14 UIKitCore                      0x103d464 __UIVIEW_IS_EXECUTING_ANIMATION_COMPLETION_BLOCK__
15 UIKitCore                      0xd14ac -[UIViewAnimationBlockDelegate _didEndBlockAnimation:finished:context:]
16 UIKitCore                      0xd0408 -[UIViewAnimationState sendDelegateAnimationDidStop:finished:]
17 UIKitCore                      0xcfb28 -[UIViewAnimationState animationDidStop:finished:]
18 QuartzCore                     0x1310c CA::Layer::run_animation_callbacks(void*)
19 libdispatch.dylib              0x3fdc _dispatch_client_callout
20 libdispatch.dylib              0x127f4 _dispatch_main_queue_drain
21 libdispatch.dylib              0x12444 _dispatch_main_queue_callback_4CF
22 CoreFoundation                 0x9a6d8 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
23 CoreFoundation                 0x7c03c __CFRunLoopRun
24 CoreFoundation                 0x80ec0 CFRunLoopRunSpecific
25 GraphicsServices               0x1368 GSEventRunModal
26 UIKitCore                      0x3a186c -[UIApplication _run]
27 UIKitCore                      0x3a14d0 UIApplicationMain
28 App                        0x9340 main + 35 (*.swift:35)
29 ???                            0x1a9aba960 (Missing)

Thanks for all the crash reports! It's a peculiar bug. Here's the code in question:

    private func generateAndEnableVerticalConstraints(of coachMarkView: CoachMarkView,
                                                      forDisplayIn parentView: UIView,
                                                      usingCoachMark coachMark: CoachMark,
                                                      cutoutPath: UIBezierPath,
                                                      andOverlayView overlayView: OverlayView) {
        let offset = coachMark.gapBetweenCoachMarkAndCutoutPath

        // Depending where the cutoutPath sits, the coach mark will either
        // stand above or below it. Alternatively, it can also be displayed
        // over the cutoutPath.
        if coachMark.isDisplayedOverCutoutPath {
            
        } else if coachMark.arrowOrientation! == .bottom {
            let constant = -(parentView.frame.size.height - cutoutPath.bounds.origin.y + offset)

            // This is the call that triggers the crash.
            coachMarkView.bottomAnchor.constraint(equalTo: parentView.bottomAnchor,
                                                  constant: constant).isActive = true
        } else {
            
        }
    }

For constant to be infinite, any of parentView.frame.size.height, cutoutPath.bounds.origin.y, or offset must be infinite. I can't think of a scenario where this would be the case, I don't think UIView frames can ever be infinite, so I guess the culprit is cutoutPath.

It would help to know how cutoutPath is generated on your end!

@ephread I just had a user report the same issue. The crash report shows the crash happening at the location you pointed to. I can't get it to crash at all on my device or any iOS simulators. The user has an iPhone 13 Pro Max running the 16.5 beta. I thought the beta was the issue but the crash is happening exactly where the other users are reporting it;

        if coachMark.isDisplayedOverCutoutPath {
            let constant = cutoutPath.bounds.midY - parentView.frame.size.height / 2

            coachMarkView.centerYAnchor.constraint(equalTo: parentView.centerYAnchor,
                                                   constant: constant).isActive = true
        } else if coachMark.arrowOrientation! == .bottom {
            let constant = -(parentView.frame.size.height -
                cutoutPath.bounds.origin.y + offset)

            coachMarkView.bottomAnchor.constraint(equalTo: parentView.bottomAnchor,
                                                  constant: constant).isActive = true
        } else {
            let constant = (cutoutPath.bounds.origin.y +
                cutoutPath.bounds.size.height) + offset

            coachMarkView.topAnchor.constraint(equalTo: parentView.topAnchor,
                                               constant: constant).isActive = true
        }
    }

I am only using one custom cutout;

    func coachMarksController(_ coachMarksController: CoachMarksController,
                              coachMarkAt index: Int) -> CoachMark {

        switch index {
        case 0:
            return coachMarksController.helper.makeCoachMark(
                for: self.navigationController?.navigationBar,
                cutoutPathMaker: { (frame: CGRect) -> UIBezierPath in
                    // This will make a cutoutPath matching the shape of
                    // the component (no padding, no rounded corners).
                    return UIBezierPath(rect: frame)
                }
            )
        case 1:
            return coachMarksController.helper.makeCoachMark(
                for: self.leftStackOutlet,
                cutoutPathMaker: { (frame: CGRect) -> UIBezierPath in
                    return UIBezierPath(rect: CGRect(x: 0, y: self.leftStackOutlet.frame.origin.y, width: self.view.frame.width, height: self.leftStackOutlet.frame.height))
                }
            )
        case 2:
            return coachMarksController.helper.makeCoachMark(for: self.allShortcutsOutlet)
        default:
            return coachMarksController.helper.makeCoachMark()
        }
    }

I managed to reproduce this issue, and get around it; maybe the same approach works for others too.

I was able to reproduce this issue by presenting a coachmark while mirroring screen from my iPhone to my Mac. At that point, there are two scenes, two windows & two screens. It's a bit tricky to debug at that point, since screen mirroring doesn't allow switching to Xcode; you have to cancel screen mirroring at the moment when you have your breakpoints ready.

I was presenting a coachmark using PresentationContext.newWindow(over:at:), because of which a new window was created to handle this presentation.

The original cause of this crash is within buildNewWindow() method, because it created a window on a scene that's connected to my Mac's monitor, instead on the iPhone's screen's scene. However, the issue with infinite constant is caused specifically by convert(rect:from) method of CoachMarkCoordinateConverter.

When calling:

let rectInInstructionsWindow = instructionsWindow.convert(rectInWindow,
                                                                  from: superviewWindow)

rectInInstructionsWindow ends up being CGRect.zero, and there's a log in the console about this issue.

I fixed the issue for myself by presenting a coachmark using PresentationContext.viewController(_:).