Skip to content

Instantly share code, notes, and snippets.

@DevAndArtist
Last active December 8, 2019 13:45
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save DevAndArtist/59b9a7d2e2316bfd4b0c6de72a98b1c0 to your computer and use it in GitHub Desktop.
Save DevAndArtist/59b9a7d2e2316bfd4b0c6de72a98b1c0 to your computer and use it in GitHub Desktop.

WWDC 2019 Labs Questions

Size changes propagation to child view controller

In A Look Inside Presentation Controllers - WWDC 2014 session the following pattern was introduced.

  • A child view controller or a presented view controller can issue a request for size update by setting its preferredContentSize property.
  • A parent view controller or a presentation controller will receive a callback on preferredContentSizeDidChangeForChildContentContainer method.
  • At this point the receiver can decide if can allow the size request and layout the child view controller or the presented view controller.
  • THE TRICKY PART: In the session it's been said that the receiver must call viewWillTransitionToSize:withTransitionCoordinator: on the calling controller before updating it. This requires an instance for a transition coordinator, however UIKit does not provide any for this case, only on device rotation or size changes of the current window.

The explained pattern makes a lot of sense, because before the size of the child is changed by its parent, it should also have the chance to prepare layout related changes. Furthermore this will trigger a chain of events as the child will forward this event to its children which is important as it can potentially change their layout.

setPreferredContentSize:         ◀───────────────────────────────┐           
                                                                 │           
                                                                 │           
                                                                 │           
┌─────────────────────┐    ┌─────────────────────┐    ┌─────────────────────┐
│                     │    │                     │    │                     │
│        Root         │    │                     │    │                     │
│     Controller      │    │    Presentation     │    │      Presented      │
│                     │    │     Controller      │    │     Controller      │
│                     │    │                     │    │                     │
│┌───────────────────┐│    │                     │    │                     │
││                   ││    └─────────────────────┘    └─────────────────────┘
││      Child 1      ││                                                      
││                   ││                                                      
│└───────────────────┘│                                                      
│┌───────────────────┐│                                                      
││                   ││                                                      
││      Child 2      ││                                                      
││                   ││                                                      
│└───────────────────┘│                                                      
└─────────────────────┘                                                      
                                                                             
                                                                             
═════════════════════════════════════════════════════════════════════════════
                                                                             
                                                                             
preferredContentSizeDidChangeForChildContentContainer:                       
                                      │                                      
                                      │                                      
                                      ▼                                      
┌─────────────────────┐    ┌─────────────────────┐    ┌─────────────────────┐
│                     │    │                     │    │                     │
│        Root         │    │                     │    │                     │
│     Controller      │    │    Presentation     │    │      Presented      │
│                     │    │     Controller      │    │     Controller      │
│                     │    │                     │    │                     │
│┌───────────────────┐│    │                     │    │                     │
││                   ││    └─────────────────────┘    └─────────────────────┘
││      Child 1      ││                                                      
││                   ││                                                      
│└───────────────────┘│                                                      
│┌───────────────────┐│                                                      
││                   ││                                                      
││      Child 2      ││                                                      
││                   ││                                                      
│└───────────────────┘│                                                      
└─────────────────────┘                                                      
                                                                             
═════════════════════════════════════════════════════════════════════════════
                                                                             
                                                                             
viewWillTransitionToSize:withTransitionCoordinator:                          
                                                                             
                                                                             
                                                                             
┌─────────────────────┐    ┌─────────────────────┐    ┌─────────────────────┐
│                     │    │                     │    │                     │
│        Root         │    │                     │    │                     │
│     Controller      │    │    Presentation     │    │      Presented      │
│                     │    │     Controller      │───▶│     Controller      │
│                     │    │                     │    │                     │
│┌───────────────────┐│    │                     │    │                     │
││                   ││    └─────────────────────┘    └─────────────────────┘
││      Child 1      ││                                                      
││                   ││                                                      
│└───────────────────┘│                                                      
│┌───────────────────┐│                                                      
││                   ││                                                      
││      Child 2      ││                                                      
││                   ││                                                      
│└───────────────────┘│                                                      
└─────────────────────┘                                                      

Here is a quote from the session (please pay attention to the bold text at the end):

Next up is preferred content size, and there are two items here: preferredContentSize and preferredContentSizeDid ChangeForChildContentContainer.

And this is methodologies for allowing a presented view controller or child view controller to say, "I need more or less size" to its container controller.

A really good example of this is the Notification Center widgets will do this.

Your notification your widget will actually set its own preferred content size, which will message its container to allow the size to change.

Let's show what this looks like in our example.

Say the presented controller wants to grow by 100 points.

So it sets its PreferredContentSize to what it would like.

And because the Presentation Controller is the container controller of the presented controller, it gets the message, PreferredContentSizeDid ChangeForChildContentContainer.

Now, if one of the Child 1 or Child 2 view controllers had requested more size, then the root view controller would have gotten this message.

So it's just messaging up to the container controller.

At this point in time, the container has a decision to make.

Does it want to honor the size request?

And if so, how much of it does it want to honor?

It can simply ignore it, if there's not any space to give.

Or it can say, "Well, you asked for 100 points, and I can give you 50."

If you were going to resize the view, it's important that you call back viewWillTransitionToSize: withTransitionCoordinator back on the view that made the request.

And this it's kind of like a receipt for that view controller, to let it know that it requested size, it's gotten a size, or however much size the container gave it.

And it's allowed them to forward that message on to any child containers it may have.

And key, right and the key point here is that after you've sent this message, the container needs to resize the child container, the child view controller.

Sending the message ViewWillTransitionToSize does not actually perform the resize itself.

That's something that the container needs to do.

This requires a custom implemenation of an object that conforms to the UIViewControllerTransitionCoordinator protocol. However since this protocol also refines UIViewControllerTransitionCoordinatorContext, Apple is not recommending to do this even though the above pattern is required and suggested by the speaker.

func preferredContentSizeDidChange(forChildContentContainer container: UIContentContainer) {
  super.preferredContentSizeDidChange(forChildContentContainer: container)
  if container === ourChildController {
    // assume we allow any size request
    let size = ourChildController.prefferedContentSize
    // create a custom coordinator (against Apple docs but as recommended in WWDC session)
    let customTransitionCoordinator = InstantNonAnimatingNonInteractiveCoordinator(
      fromController: self,
      update: { container.view.frame.size = size }
    )
    // Trigger the event chain on the child container and its children
    container.viewWillTransition(to: size, with: customTransitionCoordinator)
    // finally trigger hypothetical update
    customTransitionCoordinator.update()
  }
}

So why does apple says that it's important that the receiver should call this method manually, but also does not recommend to create a custom transition coordinator while not providing any solution from UIKit's side?

The mystery about `size(forChildContentContainer:withParentContainerSize)`

We'll stay at the same session as above. Actually the method in question was discussed in the session right before the issue with above pattern. Here is the quote from the transcription (again pay attention to the bold text):

So now that we have this set up (SEE THE VIEW CONTROLLER STRUCTURE FROM ABOVE), let's give ourselves a viewWillTransitionToSize call.

Maybe the device rotated, and we're getting a different size or different bounds for that view.

These messages always start with the root view controller.

And from there, that controller will message any child view controllers that it may have.

But the size that we pass to the root view controller is probably not the same size that you want to pass onto the child.

It's probably a smaller size.

So this is where sizeForChildContentContainer withParentContainerSize comes in.

We will ask the root controller to size Child 1, and that method returns a size that we will pass on to Child 1's viewWillTransitionToSize.

We continue along if there are any other child view controllers of this controller.

And then any controllers that are presenting view controllers that means that they have another view controller presented on top of them will have their Presentation Controller called next.

In this case, the root view controllers the child view controllers, excuse me aren't presenting anybody, but the root view controller actually is.

The root view controller is not a container for the Presentation Controller, so the size that gets passed to it is the same size that we gave to the root controller.

But the Presentation Controller is the container for the presented controller.

So we need to ask the Presentation Controller, a sizeForChildContentContainer, passing on to presentedViewController.

The messaging flow is basically has a depth for search.

You start with the root view controller.

Any child controllers that are present will get called, but any controller that is presenting a presenting another controller will have its Presentation Controller messaged and then its presented controller is > messaged.

And then, the process repeats again.

If for any reason that you implement one of these methods, and you call super, this messaging still is performed for you.

But if you implement these methods and don't call super, the object or the class that does not pass the message on any of its child controllers or any of its presented Presentation Controllers will no longer > get that message.

It's something to be aware of.

This was introduced so quickly and the importance of this has not been mentioned at all. The documentation for that method says:

Container view controllers use this method to return the sizes for their child view controllers. UIKit calls the method as part of the default implementation of the viewWillTransition(to:with:) method for view controllers. It calls the method once for each child view controller embedded in the view controller. If you are implementing a custom container view controller, you >>>>should override this method<<<< and use it to return the sizes of the contained children. View controllers and presentation controllers return the value in parentSize by default.

Any view controller that has a child view controller that it lays out is a container view controller by this logic. If you add a view controller to another view controller in Interface Builder using a container view, you already created a custom container view controller. However the size related to your layout is not automatically propagated by the system into size(forChildContentContainer:withParentContainerSize).

Almost no one in the community implements this method. This is a bad thing as nested view controllers that are not full screen will get wrong size values from viewWillTransition(to:with:).

UIKit container view controllers such as UINavigationController, UITabBarController or UISplitViewController do all override the method in question and report a smaller size to its children when needed.

Why is this importance not communicated more loudly?

Core Bluetooth questions
  • If I have multiple peripherals where the I need subscribe to 50+ characteristics on each, what is the preffered DispatchQueue to initialize the CBCentralManager? The received values will be used to compute the state the periphal is in, then cached and finally trigger updates for the UI.

  • On Android there is a limit for the maximum amount of characteristics an app can subscribe to set to 15 (this is super random an an issue for our android app). Does Apple have a similar maximum bound for characteristics an app can subscribe to per peripheral?

  • What is the best way to find out how to minimize the risk of the app being killed if we're trying to do background connects to Bluetooth? One feature is that our peripherls ask for location data (with users permission only) from the phone over bluetooth even in background. This is a very important feature, but we have issues to test it as it's only possible on a real device as the simulator on macOS has no bleutooth support. Tests over night are hard or impossible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment