7 Replies
      Latest reply on Nov 29, 2019 1:46 PM by uncletr
      uncletr Level 1 Level 1 (0 points)

        I have an imageView which has a top constraint IBOutlet called imageViewTopConstraint.   In viewWillLayoutSubviews( ) I set the imageViewTopConstraint.constant value.   The imageView displays properly for all iPhone models while in portrait mode, which is good.

         

        When I switch to landscape mode, I want to disable the imageViewTopConstraint, so the viewWillLayoutSubviews( ) has the following code:

         

        if UIDevice.current.orientation.isPortrait {
           imageViewTopConstraint.constant = (2 * screenHeight) / 3
           imageViewTopConstraint.isActive = true
        }
        else if UIDevice.current.orientation.isLandscape {
           imageViewTopConstraint.isActive = false
        }

         

        Issue

        When I run the application it is initially in portrait mode.   When I switch to landscape mode I receive the following fatal error at the line where I set "imageViewTopConstraint.isActive = false" to disable the constraint (line 6 above)

         

              Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value

         

        Any idea what is going on or what this means?

        • Re: Setting constraint "isActive=false" causes fatal error
          Claude31 Level 8 Level 8 (7,255 points)

          To be sure of what happens, please add some log:

           

          if UIDevice.current.orientation.isPortrait {
             imageViewTopConstraint.constant = (2 * screenHeight) / 3
             print("isPortrait  imageViewTopConstraint",  imageViewTopConstraint)
             imageViewTopConstraint.isActive = true
          }
          else if UIDevice.current.orientation.isLandscape {
             print("isLandscape  imageViewTopConstraint before",  imageViewTopConstraint)
             imageViewTopConstraint.isActive = false
             print("isLandscape  imageViewTopConstraint after",  imageViewTopConstraint)
          }

           

          and tell what youi get when you turn landscape. Notably if this is called several times.

           

          Probably, you have removed the constraint somewhere making it nil.

          • Re: Setting constraint "isActive=false" causes fatal error
            DelawareMathGuy Level 3 Level 3 (110 points)

            hi uncletr,

             

            this sounds familiar to me ... a constraint reference unexpectedly being nil when trying to tweak a layout between portrait and landscape.

             

            please let us know how the constraint is defined: is it defined in IB and then referenced from your code, or is it defined and added directly in code, say in viewDidLoad()?

             

            in my recent case, i had a reference to a constraint defined in IB; when i set it to active = false, the constraint reference immediately became nil, almost exactly as you describe. 

             

            looking forward to more info,

            DMG

            • Re: Setting constraint "isActive=false" causes fatal error
              uncletr Level 1 Level 1 (0 points)

              DelawareMathGuy,

               

              I defined the constraint in IB (using the little tie-fighter icon), then highlighted the constraint and dragged it into the code and assigned an IBOutlet name to the constraint.

               

                  >>when i set it to active = false, the constraint reference immediately became nil

                  I believe this is exactly what is happening to me (please read below).   This feels wrong to me.

               

              Claude:

               

              If I add the "print" statements like you requested (as below) ...

               

                  print("isPortrait  imageViewTopConstraint",  imageViewTopConstraint)

               

              then I see the following warnings produced for that "print" line

               

                  Coercion of implicitly unwrappable value of type 'NSLayoutConstraint?' to 'Any' does not unwrap optional

                  Provide a default value to avoid this warning

                  Force-unwrap the value to avoid this warning

                  Explicitly cast to 'Any' with 'as Any' to silence this warning

               

              That being said, since these are just warnings, I ran the code anyway.

               

              Results

               

              ViewWillLayoutSubviews

              isPortrait  seatPos1TopAlignment_Constraint =  Optional(<NSLayoutConstraint:0x600000a53c00 UIView:0x7f9d937659f0.top == UILayoutGuide:0x60000107cb60'UIViewSafeAreaLayoutGuide'.top + 313.033   (active)>)

               

              ViewWillLayoutSubviews

              isPortrait  seatPos1TopAlignment_Constraint =  Optional(<NSLayoutConstraint:0x600000a53c00 UIView:0x7f9d937659f0.top == UILayoutGuide:0x60000107cb60'UIViewSafeAreaLayoutGuide'.top + 313.033   (active)>)

               

              ViewWillLayoutSubviews

              isPortrait  seatPos1TopAlignment_Constraint =  Optional(<NSLayoutConstraint:0x600000a53c00 UIView:0x7f9d937659f0.top == UILayoutGuide:0x60000107cb60'UIViewSafeAreaLayoutGuide'.top + 313.033   (active)>)

               

              ViewWillLayoutSubviews

              isPortrait  seatPos1TopAlignment_Constraint =  Optional(<NSLayoutConstraint:0x600000a53c00 UIView:0x7f9d937659f0.top == UILayoutGuide:0x60000107cb60'UIViewSafeAreaLayoutGuide'.top + 313.033   (active)>)

               

              ViewWillLayoutSubviews

              isPortrait  seatPos1TopAlignment_Constraint =  Optional(<NSLayoutConstraint:0x600000a53c00 UIView:0x7f9d937659f0.top == UILayoutGuide:0x60000107cb60'UIViewSafeAreaLayoutGuide'.top + 313.033   (active)>)

               

              ViewWillLayoutSubviews

              isLandscape  seatPos1TopAlignment_Constraint  before = Optional(<NSLayoutConstraint:0x600000a53c00 UIView:0x7f9d937659f0.top == UILayoutGuide:0x60000107cb60'UIViewSafeAreaLayoutGuide'.top + 313.033   (active)>)

              isLandscape  seatPos1TopAlignment_Constraint  after =  nil

               

              ViewWillLayoutSubviews

              isLandscape  seatPos1TopAlignment_Constraint  before =  nil

              Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value: file /Users/unclet/Dads Stuff/Swift Projects/Backup/OhHell 3/OhHell/GameplayViewController.swift, line 3313

               

               

              QUESTION

              You were correct, the constraint moved to a NIL state, however, I am still not sure how it gets set to NIL.  Does setting the constraint "isActive = false" make the constraint NIL?   This does not seem correct.

                • Re: Setting constraint "isActive=false" causes fatal error
                  Claude31 Level 8 Level 8 (7,255 points)

                  The problem is that viewWilllayout is called several times.

                   

                  Second time, you have set the constraint as inactive, which means it has been removed (it will be reinstalled when you set to true).

                   

                   

                  So one way is to test

                   

                  if UIDevice.current.orientation.isPortrait {
                     imageViewTopConstraint.constant = (2 * screenHeight) / 3
                     imageViewTopConstraint.isActive = true
                  }
                  else if UIDevice.current.orientation.isLandscape {
                     if imageViewTopConstraint != nil { imageViewTopConstraint.isActive = false }
                  }
                    • Re: Setting constraint "isActive=false" causes fatal error
                      uncletr Level 1 Level 1 (0 points)

                      Thanks for the support Claude.

                       

                      Please confirm:

                       

                      1) To avoid setting isActive=false multiple times, I would need to check whether the constraint is NIL before setting isActive=false inside the landscape "if" statement again.

                       

                      2) Inside the portrait "if statement", I would need to first set isActive=true before setting the constraint.constant value again so the constraint can be created again in the system before setting it

                       

                      PS:  I still believe setting "isActive = false" should not make the constraint NIL as this makes no sense.   Having a "constraint.installed" property associated with the constraint would be better for setting the constraint to NIL.   The term "isActive" typically refers to "enabled/disabled" settings and not associated with "removal/deletion".   My two cents ... for what it is worth.

                        • Re: Setting constraint "isActive=false" causes fatal error
                          Claude31 Level 8 Level 8 (7,255 points)

                          I cannot be 100% sure that iOS behaves exactly like this, so it's more a rule of thumb than a definite statement…

                           

                          Here is what doc says : https://developer.apple.com/documentation/uikit/nslayoutconstraint/1527000-isactive

                          deactivating the constraint calls removeConstraint(_:)

                          ___________________

                          Declaration

                          var isActive: Bool { get set } 

                          Discussion

                           

                          You can activate or deactivate a constraint by changing this property. Note that only active constraints affect the calculated layout. If you try to activate a constraint whose items have no common ancestor, an exception is thrown. For newly created constraints, the isActiveproperty is false by default.

                          Activating or deactivating the constraint calls addConstraint(_:) and removeConstraint(_:) on the view that is the closest common ancestor of the items managed by this constraint. Use this property instead of calling addConstraint(_:) or removeConstraint(_:) directly.

                          _________________________

                           

                           

                          1) To avoid setting isActive=false multiple times, I would need to check whether the constraint is NIL before setting isActive=false inside the landscape "if" statement again.

                          Yes. What you have to avoid is calling a property on a constraint that has been removed

                           

                          2) Inside the portrait "if statement", I would need to first set isActive=true before setting the constraint.constant value again so the constraint can be created again in the system before setting it

                          Logically, yes. Did you try both ?

                           

                          PS:  I still believe setting "isActive = false" should not make the constraint NIL as this makes no sense.   Having a "constraint.installed" property associated with the constraint would be better for setting the constraint to NIL.   The term "isActive" typically refers to "enabled/disabled" settings and not associated with "removal/deletion".   My two cents ... for what it is worth.

                          I agrree, it is confusing. But I think that was needed for the constraint manager to work more efficiently (my 1 cent here!).