Friday, August 12, 2011

Writing high-quality view controller containers

In a previous article, I mentioned high-quality view controller containers as an essential tool to help you manage your view controller hierarchy efficiently. Writing such objects is difficult to get right, though, since many issues have to be faced simultaneously.

In this article, we will define which requirements view controller containers should fulfill and, of course, how they can be implemented. This should not only provide you with hints about containers themselves, but also about view controllers in general.

To my great surprise, though view controllers are part of the everyday life of an iOS programmer, I discovered they are often misused and abused, probably due to a lack of understanding. Programmers often have a way to quickly judge the quality of some code base, mine for iOS projects is to look at how its view controllers are implemented. Implementations paying attention to details like memory exhaustion surely deserve more trust and interest than implementations barely tweaking the UIViewController Xcode template.

Before I start, please apologize for the length of this article. There is so much to be said about containers and view controllers in general that I thought the subject deserved an extensive treatment. I humbly hope this article will help demystifying this class and make you a more proficient iOS programmer.

Examples of view controller containers are available from the CoconutKit library. You may want to have a look at their implementations after you have read this article.

A definition

View controller containers are objects whose responsibility is to manage some set of view controllers, displaying or hiding them as required. Often, containers are view controllers themselves (UITabBarController, UINavigationController or UISplitViewController, for example), but this is is by no means required (UIPopoverController, for example, directly inherits from NSObject).

A remark about naming: When speaking about container objects in general, I use the term "view controller container", or simply "container". When speaking about containers which themselves inherit from UIViewController, I specifically use the term "container view controller". Please keep this distinction in mind.

Before delving into specifics, let me introduce a practical example of a container object.

Practical example

One common problem with view controllers is that they tend to become quite fat as a project evolves. You start with a small, clean view controller princess, and you end up with a clumsy, cluttered toad superimposing many views, powered up by thousands of lines of code barely sustainable.

In one iPad project I worked on for a bank, we had to display a detailed view for a fund. This view was made of three sections, one for a performance graph, another one for various facts about the fund (and made of several table views), and the last one for documents. We knew from the beginning that other sections would be added in the future.

Instead of stuffing everything into a single view controller, we split up our content into three view controllers: Graph data, fund facts and documents. Those view controllers were then embedded into another view controller containing some general fund information and a UISegmentedControl to toggle between sections. The result can be represented as follows, where the blue rectangle corresponds to the area where the embedded view controllers are drawn:

ViewController containers fund

This is in fact a common situation I face when writing iPad applications: I often need to embed a single view controller into another one. And since I face this problem so many times, I decided to write a custom container object, HLSPlaceholderViewController, to make my life easier. Here are the requirements I decided my container implementation had to fulfill:

  • The container is itself a view controller, from which programmers must inherit to create their own
  • The container view controller must be customizable, either using a xib or programmatically. There must be a way to define a placeholder area where the embedded view controller's view will be drawn
  • Only one embedded view controller can be displayed at a time. The current view controller can be swapped with another one simply by assigning a new view controller to a property
  • Built-in transition effects similar to those usually encountered on iOS must be available when changing the embedded view controller (push, fade, etc.)
  • The view controller displayed in portrait orientation might be completely different from the one displayed in landscape orientation. Some mechanism should be available to swap view controllers transparently during rotation

  • An HLSPlaceholderViewController container can be used to compose more sophisticated interfaces. Here is an example where one is used to create some kind of split view controller. The HLSPlaceholderViewController view contains a table view on the left and a placeholder area on the right (blue rectangle), where embedded view controllers are drawn. When clicking on an item on the left, a corresponding view controller is displayed on the right:

    ViewController containers embedding

    The requirements listed above pretty much covers every aspect I usually expect from a view controller container. While not every view controller container requires this customization level, all must exhibit common properties we discuss in the next section.

    View controller container properties

    A view controller must at least fulfill two major requirements which I already mentioned in one of my previous articles about view controllers:

  • Containers must retain the view controllers they manage. This is consistent with existing built-in UIKit containers, and it would be really bad if this was not the case. Otherwise programmers using a container would have to retain and release the view controllers as they are needed by the container. This would put more burden on programmers who, after all, cannot in general know when the container is done with its contents (it would not really make sense to have delegate methods called for this purpose, wouldn't it?)
  • Containers must correctly forward lifecycle and rotation events to the view controllers they manage

  • While the first requirement should be obvious, the second one deserves more attention since it is a tricky part in getting a container implementation right.

    The view controller lifecycle

    If I were to give awards to the most important documentation pages an iOS developer must read, the View Controller Programming Guide for iOS would certainly be among my favorite ones. Before iOS 5, this guide contained so much information (especially in the Custom View Controllers section) it could be quite overwhelming at first. With the new iOS 5 storyboard, the guide has been updated and is now sadly far less comprehensive that it was. Any iOS developer, though, and any container implementer in particular, should still read this guide more than once, though, because it is the only way to understand some of the subtleties associated with view controllers.

    The iOS documentation and the UIViewController header file define the lifecycle of the view associated with a view controller. Let me recall it and pinpoint its numerous pitfalls:

  • A view controller's view does not exist until you access the UIViewController's view property. The first time you access it, the view gets lazily created, either by calling a loadView method if one has been implemented, or by deserializing the contents from a xib (I prefer using xibs most of the time since they make my life far easier when designing views). If you need to test whether a view controller's view has been created, you must call the isViewLoaded method of UIViewController. You cannot of course just test the view property since this would trigger lazy view creation!
  • After the view has been created, the viewDidLoad method is called. When implementing it, and if your view was created from a xib, you can use the outlets bound using Interface Builder to further customize your view programmatically. Be careful that the view dimensions are not final yet: They are the raw dimensions obtained right after the view has been loaded, but these will probably still be adjusted to take into account other screen elements like the status bar or a navigation bar. You should therefore be careful not to perform any view frame adjustments relying on your final application frame in the viewDidLoad method (since the final frame is not known yet). In general, the viewDidLoad method is a perfect place for skinning purposes (colors, fonts), setting localized strings or performing the initial synchronization between model data and view
  • The viewWillAppear: method is called. At this point, the final application frame is known and you can perform view size adjustments. The viewWillAppear method is called before any animation displaying the view begins (whether the view has already been added as subview or not is irrelevant as far as I know)
  • The viewDidAppear: method is called after the animation displaying the view finishes

  • These are the events related to view creation and display. Two additional events occur when the view disappears:

  • When the view is about to disappear, the viewWillDisappear: method is called. This happens before any animation hiding the view starts
  • After the animation hiding the view finishes, the viewDidDisappear: is called. Note that the view does not need to be removed from its superview or released when it has disappeared

  • Moreover, if a memory warning is encountered during the time the view is loaded (whether it is actually visible or not), the UIViewController didReceiveMemoryWarning method is called. If the view controller's view is invisible, this method takes care of setting it to nil, calling the viewDidUnload method afterwards. To free as much resources as possible, the viewDidUnload method is therefore where you should set your view controller's outlets (and related view resources) to nil.

    In essence, the view lifecycle above is a formal contract:
  • When implementing a view controller, it gives you guidelines and hints about where you should insert your code so that the view controller displays correctly, efficiently, and properly responds to low-memory conditions
  • When implementing a container, it defines the behavior you must implement so that users of your container can rely on the usual view lifecycle to occur as expected. It would have been nice if this was guaranteed automagically, and it is (to some degree) if you are using the new iOS 5 containment API. But in general you still have to do the hard work

  • If a view controller container fails to implement the view lifecycle correctly (i.e. if it implements its own non-standard lifecycle), it gets poisonous and should not be used. Imagine you are implementing a view controller you want to display using such a container, and you discover that your viewWillAppear: event is not fired correctly. What can you do? If you are lucky you might be able to stuff everything in viewDidAppear: (if it gets called correctly, which I would not count on). If you are not lucky you might end up having to stuff everything in the viewDidLoad method, which is far from optimal since at that time view dimensions are not reliable (well, if the container implementation is *really* bad, they might be, making things even worse). Basically, the result you get is a view controller tightly bound to a container's implementation, not to the view lifecycle contract. Such objects are most probably incompatible with other view controller containers, and if you later need to refactor your application to use other containers, you will probably have to move code around to get it to work.

    View controllers are also governed by a contract for orientation changes. We discuss it in the next section.

    Handling rotation

    As for the view lifecycle, rotation events are precisely defined as well. Two kinds of rotations are available:

  • two-step rotation, which generates events for the two halves of the rotation process
  • one-step rotation, which treats the rotation as a whole

  • One-step and two-step rotations cannot coexist in a same view controller implementation. If one-step rotation methods are discovered, two-step rotation methods are ignored. But this is not a severe issue since two-step rotation is deprecated (and even formally starting with iOS 5). I strongly advise you to forget about its existence altogether, and I will only discuss one-step rotation in the following.

    For one-step rotation, four methods are relevant:

  • When the interface orientation changes as a result of the user rotating the device, the shouldAutorotateToInterfaceOrientation: method is called to know if the view controller supports the new orientation
  • If the answer is YES, willRotateToInterfaceOrientation:duration: is called
  • The willAnimateRotationToInterfaceOrientation:duration: method is called just before the rotation animation starts. At this point, the final frame after rotation is known, which makes it a perfect place for frame adjustments
  • When the rotation animation ends, didRotateFromInterfaceOrientation: is called

  • Besides those methods which get called for you when rotation occurs, the UIViewController interface offers an interfaceOrientation method which you can call to get the current interface orientation. The problem with this method is that it is only reliable when the view controller is displayed in a standard way (i.e. using a built-in UIKit view controller container, calling a method for modal display, or when a view controller is added as first subview of the application main window). There is a way to solve this issue, though, but we will discuss it later.

    Until now, we have discussed documented properties of a view controller. Other common traits arise when using view controllers, both for convenience or efficiency reasons. We discuss them in the next section.

    Informal properties of view controllers

    Besides lifecycle and rotation events, view controllers embedded into containers exhibit some common properties:

  • When a view controller gets inserted into a container, it should be able to know which container it was inserted in (as with the UIViewController navigationController or tabBarController properties, for example). Moreover, UIKit built-in containers prevent simultaneous insertion of the same view controller into several containers, a behavior we should expect for custom containers as well
  • A view controller's view should be instantiated when it is really required to avoid wasting resources prematurely
  • When in a container, user interaction is usually restricted to the view controller on top. Even if other view controllers stay visible underneath (e.g. through transparency), interaction with them is usually not desired
  • View controller properties for use in a navigation controller, e.g. navigation elements and title, might need to be forwarded through a custom container for which the view controller is currently the active one. This way, when itself embedded into a navigation controller, the container view controller is able to display the navigation elements of the currently active child view controller. Forwarding navigation elements through the container does not always makes sense, though, this behavior must therefore be optional. In the example below, where forwarding has been enabled, the title of the embedded view controller (in blue) becomes the one of its container, and is thus displayed in the navigation bar:

    ViewController containers forwarding

  • Usually, when you hand over a view controller to a container, you do not keep any additional reference to it since it is already retained by the container. When it gets removed from the container, and since no additional strong references exist, it simply gets deallocated, releasing the associated view at the same time. But since view controller's views can be costly to create, it sometimes make sense to keep an additional external strong reference to a view controller inserted into a container. This way, when the view controller is removed from the container, it does not get destroyed, and neither does its view. For this caching mechanism to work, a container implementation must therefore never release the view associated to a view controller when it gets removed. As a corollary, this means that a container is only allowed to release a view when it needs to save memory. This usually has to be made after a memory warning has been received. For containers managing a large number of view controllers, this can also happen when the container decides to limit the number of views loaded at the same time. The picture below shows some container managing a stack of view controllers (only one is visible, but all currently inserted view controllers have been made visible for the sake of the explanation). In this case, the container only keeps the three topmost view controllers loaded, the views of the other ones have been unloaded:

    ViewController containers topmost loaded


  • A remark about properties allowing to retrieve the identity of the container view controller: Besides the navigationController and tabBarController properties, UIViewController also exhibits a read-only parentViewController property. According to UIKit documentation, "parent view controllers are relevant in navigation, tab bar, and modal view controller hierarchies" (iOS 4 documentation, with slight changes in iOS 5). Since custom containers are also parent view controllers in some way, it would be tempting to have this method return custom containers as well. This can in fact be achieved using method swizzling, and it seems to work quite well at first: When the parentViewController property has been swizzled, the interfaceOrientation property suddenly returns the correct orientation (in fact the one of the container), and the navigation bar title is forwarded from the content view controller to its container view controller transparently. Seems quite nice.

    But doing so, there is no way to control whether or not we want the title to be forwarded or not. We therefore sadly cannot swizzle parentViewController if we want this flexibility (and I definitely wanted it). This seems to reveal the fact that Apple's vision of view controller containers was restricted to view controller containers like UITabBarController or UINavigationController. For such containers, forwarding perfectly makes sense in all cases since they display their contents "full screen".

    In general, though, a container can display several view controllers simultaneously, in which case property forwarding may not make sense. Even if a single view controller is displayed, a container might want to have its own title. This is why the UIViewController parentViewController property should IMHO never be swizzled to return custom containers, even if this would have made sense.

    Designing and implementing a view controller container

    Now that we have extensively discussed the many details which we must pay attention to when dealing with view controllers, let us start discussing how a view controller container can be implemented. It took me several attempts to finally get a satisfying design and properly behaving objects. I here merely concentrate on general guidelines and hints, have a look at the full implementations in CoconutKit for more juicy details.

    Useful tools

    Before starting to write a container, you should consider adding a set of test view controllers to your toolbox. Here is mine for CoconutKit, and it proved to be extremely useful:

  • A view controller logging lifecycle and rotation events to the console
  • Two view controllers with stretchable / non-stretchable views
  • A semi-transparent view controller to test stacking up view controllers
  • View controllers supporting only portrait, respectively landscape orientations
  • A view controller whose view is costly to create. Such an object can easily be simulated by calling sleep in its viewDidLoad method
  • A view controller customizing the title and navigation items when displayed (and providing a way to change these properties when already displayed)

  • In general, I use a random color as view background color, assigned in the viewDidLoad method. This way, I am able to easily see when a view controller has been unloaded and reloaded.

    Moreover, you definitely need a way to test the behavior of your container when memory warnings are received. The easiest way IMHO is to present a view controller modally in the simulator on top of the container you are writing and testing, and to fake a memory warning before dismissing it. This helps you find quickly when your container fails at releasing views, but it should not prevent you from checking the process in detail at least once by setting breakpoints or adding logging statements: I discovered a nasty bug in one of my view controllers this way

    The toolbox being ready, let us discuss the implementation.

    Managing view controllers in a container

    As I was iterating through container implementations, I finally started to identify behavior traits common to view controllers managed by containers. Those traits are all formal and informal requirements we discussed previously. The most important step was to isolate these traits into a single class which can be reused by several container implementations. I called this class HLSContainerContent and, as its name suggests, its only purpose is to help managing the contents of a container, i.e. its view controllers.

    An instance of HLSContainerContent acts as a kind of smart pointer, taking ownership of a view controller when it is handed over to a container, and releasing ownership when the object is destroyed. Instead of having the container directly retain the view controllers it manages (which is the proper semantics to have, as we already discussed), the container retains HLSContainerContent instances, each one wrapping a single view controller it retains. The end result is therefore the same.

    What is interesting with smart pointer objects is that they can set up a context which stays valid during the time ownership has been acquired. In our case, this context can store the container controller into which a view controller has been inserted. This allows us to prevent simultaneous insertion into several containers, as well as to implement methods returning the parent container of a view controller. For built-in UIKit containers, such methods have been added using categories on UIViewController, we should therefore do the same. Since we cannot add any instance variables to the UIViewController class for storing which custom container it may have been inserted to (and this is not the way I would implement it if I could), we must resort to some kind of global mapping between view controllers and container objects. This could be implemented using a global dictionary, since there are no multithreading issues to consider here (UIKit is inherently meant to be used only from the main thread). Another convenient solution to achieve the same result is to use associated objects (see runtime.h). This namely offers an easy way to virtually add instance variables to classes which we do not control the implementation of, but beware if your code must be thread-safe.

    Managing content is the primary goal of the HLSContainerContent class, but not its only one, as we will see later.

    Managing view controller's views

    As said before, accessing the view property of a view controller lazily creates the view. It is therefore especially important never to access this property sloppily. To reduce the probability of making mistakes when implementing a container, I decided that view operations should only occur through HLSContainerContent. While I did not implement any mechanism to enforce it, it is easy to check that a container implementation never directly access some view controller's view property directly.

    The interface of an HLSContainerContent object exhibits four methods used to manage the view associated with the view controller it wraps:

  • A method to add the view controller's view where the containers draws its content (container view). If the view does not exist yet (it might already exist for caching purposes, see below), it gets instantiated and added to the container view. Some containers might manage a large number of view controllers, in which case they must be able to unload hidden views to save memory. When some unloaded view is about to be later displayed, it again needs to be instantiated and added. This is why additional HLSContainerContent objects can be provided as an additional parameter. Those represent the contents of the container, so that the view can be precisely inserted into the current container view hierarchy
  • A method to remove the view from the container view it was added to. This method does not release the view so that it might be reused without having to build it again from scratch
  • A method to release the view when it is not needed anymore, or when memory conditions degrade
  • Finally, a method returning the view if it has been instantiated, nil otherwise. This prevents the view from being instantiated too early

  • We previously discussed view caching, and why a container should not release the view associated with a view controller when it gets removed. For HLSContainerContent, this translated into separate methods for removing the view and releasing it. Moreover, the HLSContainerContent dealloc method only removes the view but does not release it. This way, even after a view controller has been removed from a container, its view can readily be used again if the view controller was retained somewhere else for caching purposes.

    Transition animations

    With UIKit built-in view controller containers, you cannot choose which transition animation you want to use when displaying a view controller. This is only possible when a view controller is presented modally, but I must admit I am not really fond of the way modal transitions are implemented in UIKit: A modalTransitionStyle property on UIViewController? Seriously, why not having an additional parameter to presentModalViewController:animated:? For my custom container interfaces, I thus decided to set transition styles using a parameter given to the various methods used for presenting view controllers. This is not only clearer from a user point of view, but also far cleaner in terms of object dependencies: The modalTransitionStyle UIViewController has nothing to do in UIViewController, since UIViewController objects should not be concerned about which objects display them. Imagine all nasty cyclic references this could lead to if all containers decided to do the same...

    From a usability point of view, when a view controller is added with some animation, it must be removed with the exact same inverse animation so that the user does not get confused. It therefore makes sense to use HLSContainerContent to store the transition style with which a view controller has been presented. Containers which work as a stack can later use this information when the view controller is popped.

    A question naturally arises: Since the animation style is stored in HLSContainerContent, why not implement the animation code in HLSContainerContent as well so that it can be shared by all custom containers? This is what I decided to do, and the resulting code is both shorter and easier to maintain, providing all my custom containers with a variety of animations, from the usual UIKit animations (cross-dissolve, navigation controller animations) to more exotic ones. And if I later think about a new animation, all I have to do is update a single class and all my containers can readily use it. Pretty cool, uh?

    When implementing a container, being able to easily generate animations and corresponding reverse animations is especially important. Imagine a container managing a stack of view controllers. When you pop a view controller, you cannot be sure that the views and frames you have to animate are the same ones you animated when the view controller was pushed. Rotations might namely have occurred in between, or maybe the views were unloaded to save some memory (e.g. as a result of a memory warning). For this reason, I recommend building a transition animation right when you need it, based on the current context (the context being which transition style to use, whether we must play the reverse animation or not, and which views are animated). This way, you guarantee that your animation looks just right. As a corollary, be especially careful if you want to cache animation parameters. Consider whether it is justified from a performance point of view (hint: Probably not), and if it really is do not forget to invalidate your cache when a rotation or a memory warning occur.

    In my case, I decided to implement a single HLSContainerContent public method through which I can get a transition animation for a view controller when I need it. To get the correct animation, this method expects some context data, like the current container contents and where views are drawn. This method then simply returns an HLSAnimation object, which encapsulates various animation details: Which views are animated, how they are, whether interaction is disabled during animation, etc. The HLSAnimation class also provides a way to generate the inverse animation with a single method call, even if the animation is complex, made of several steps and involving several views.

    Lifecycle events

    When implementing containers, the following two measures must be taken so that view lifecycle events reach contained view controllers correctly:

  • When a transition animation occurs, call the viewWillDisappear: and viewWillAppear: methods before the animation begins, for the view controllers which gets removed, respectively for the one which gets displayed. The animation start callback is the perfect place to insert this code. Conversely, call the viewDidDisappear: and viewDidAppear: methods in the animation end callback.
  • If the container is itself a view controller, it must forward the lifecycle events it receives to the currently active view controllers. This usually means sending the same events to the currently visible view controllers. For example, for a container stacking up view controllers, this would be the top one, and for a split view controller both view controllers involved. Getting this behavior is not difficult: Implement each view lifecycle method of the container view controller, call the super method, get the currently active view controllers, and call the same lifecycle method for each of them

  • Let us now discuss rotation.

    Rotation events

    If your container is a view controller, implement the usual one-step animation callbacks for it. This means:

  • The shouldAutorotateToInterfaceOrientation: should call the same method on all view controllers it contains. If a single one of them does not support the new orientation, return NO for the container as well.
  • In all subsequent one-step rotation events, call the super method, get the currently active view controllers and call the same rotation method for each of them

  • If your container is not a view controller (a case I admit I never had to implement until now), I conjecture your container should register for the UIApplicationWillChangeStatusBarOrientationNotification and UIApplicationDidChangeStatusBarOrientationNotification, and call the rotation methods for its contained view controllers from the associated callbacks. If you already had to implement such a container, I would be pleased to hear about your experience. I will try to update this article once I know more about this case.

    In my view controller container implementations, I try to allow view controllers to be different depending on the interface orientation. In fact I defined a protocol, HLSOrientationCloner, which view controllers can implement to return a clone of themselves with a different orientation. In such cases, I use the willRotateToInterfaceOrientation:duration: method to instantiate the clone, and to start installing it with a cross-dissolve animation having the same duration as the rotation animation. If frame adjustments have to be done, those must not be made prior to willAnimateRotationToInterfaceOrientation:duration: being called.

    One last piece of advice about rotations if your container is a view controller: I strongly recommend disabling rotation when a transition animation is playing. This could lead to undesired side-effects and is difficult to test. This should not be a nuisance to end-users who just need to try rotating their device again when the transition animation is over. An easy way to implement this behavior is to maintain an animation counter (incremented in an animation start callback, and decremented in the animation end callback), and to test it in the shouldAutorotateToInterfaceOrientation: method. If a running animation is discovered, just return NO to prevent rotation.

    Pre-loading view controllers into a container

    The user should always be able to pre-load a container with view controllers before it is actually displayed. Using HLSContainerContent, this was in fact easy to achieve. Pre-loading namely does not inadvertently lead to view creation, and all properties about the view controller and the associated transition animation are encapsulated as well. All that remains to do is to implement the viewWillAppear: container method (frame dimensions are not reliable earlier) to add the pre-loaded view controller's views to the container view.

    Handling a large number of view controllers

    Some containers might be able to load a large number of view controllers. Most of the time, such containers should fall into one of the following categories:

  • Containers with push-pop behavior: For such containers, it might make sense to only keep a limited number of topmost views alive, and to release those deeper (probably invisible anyway)
  • Containers which allow to browse a large number of view controllers, all of which should be accessible right from the beginning: In such cases, view controllers should be loaded lazily and released when appropriate, to avoid having to load them all right from the start (this could be costly even if no view is instantiated). How the release strategy should be implemented greatly depends on the container behavior. For example, if a container allows to browse view controllers in a contiguous manner, view controller's views far enough should be released

  • The code for handling large number of view controllers is inherently part of your container implementation, though again HLSContainerContent is designed to make view unloading and reloading easier. In most cases, a data source protocol can help formalizing the way view controllers are loaded.

    Property forwarding

    Last but not the least, HLSContainerContent also provides view controllers inserted into container view controllers with the ability to "see through" the container they are in, directly reaching any navigation controller the container is itself in. To achieve this result, the following properties have to be forwarded transparently through the container when this behavior is desired:

  • viewController.title
  • viewController.navigationItem.title
  • viewController.navigationItem.backBarButtonItem
  • viewController.navigationItem.titleView
  • viewController.navigationItem.prompt
  • viewController.navigationItem.hidesBackButton
  • viewController.navigationItem.leftBarButtonItem
  • viewController.navigationItem.rightBarButtonItem
  • viewController.toolbarItems
  • viewController.hidesBottomBarWhenPushed

  • As discussed previously, this cannot be simply achieved by having parentViewController return the container view controller. In the case of HLSContainerContainer, this was implemented by swizzling the following UIViewController getters and setters (thanks 0xced for the original idea and implementation):

  • navigationController
  • navigationItem
  • setTitle:
  • setHidesBottomBarWhenPushed:
  • setToolbarItems:
  • setToolbarItems:animated:

  • When one of the above getters is called and forwarding is enabled, we check if the view controller has been associated with a container view controller. If this is the case, the getter is recursively called on the container view controller, otherwise the original implementation is called. Having the getter recursively called ensures that forwarding works even if containers are nested.

    Similarly, when one of the above setters is called and forwarding is enabled, the original implementation is called, and if the view controller is associated with a container view controller, the setter is also called on it, propagating the change further away.

    Since we cannot simply swizzle the parentViewController method, we also have to fix the interfaceOrientation UIViewController read-only property. This is easily achieved by swizzling it, so that when a view controller is associated with a container view controller, the container orientation is returned.

    The above approach works but is not perfect from a maintenance point of view, though: If more properties are added in the future, more methods will need to be swizzled. Any suggestion is welcome.

    Examples of view controller containers

    Now that we have discussed containers extensively, here are some examples of containers you might want to implement:

  • A container view controller embedding a single view controller (this is the practical example I described at the beginning of this article). My implementation is called HLSPlaceholderViewController. This container can be used to easily implement some kind of tab bar controller, for example
  • A container view controller managing a stack of view controllers. I implemented one, called HLSStackController, and which takes care of only keeping only a few top view controller's views loaded (this is a setting which can be tweaked if needed). Unlike what happens when view controllers are displayed modally, it can be used to show view controllers transparently on top of other ones
  • A container view controller displaying several view controllers side-by-side in a scroll view, one being visible at a time (maybe with paging enabled or periodic boundaries)
  • A container view controller displaying two view controllers side-by-side (similar to UISplitViewController)

  • I started with HLSPlaceholderViewController and HLSStackController since these containers correspond to basic building blocks which can be nested to create more complex containers. For example, by embedding an HLSStackController into an HLSPlaceholderViewController, you could create a new kind of navigation controller. By properly designing more high-quality containers and ensuring they nest properly, you can add precious tools to your iOS programmer toolbox. Start now!

    One final remark about naming conventions I apply: I tend to name containers as HLS<SomeName>ViewController if they ultimately inherit from UIViewController and if they are intended to be subclassed when used. In all other cases, I simply call containers HLS<SomeName>Controller. This rule more or less fits the naming scheme applied for UIKit built-in view controllers, with some exceptions though (UISplitViewController, for example).

    iOS 5

    With the iOS 5 SDK, Apple engineers have introduced new API methods to help writing view controller containers. The documentation is currently rather scarce, but it seems the API can be quite useful to implement basic containers. It hasn't all the features of HLSContainerContent, but it is still a nice addition. It namely relieves the programmer from having to call most view lifecycle events (this is done for you), has support for transition animations, and introduces a clear parent-child relationship between a container and the view controllers it displays.

    There is a major issue with this new API, though: Existing pre-iOS 5 container implementations need to be updated to run on iOS 5, otherwise silly bugs will appear (most notably doubled lifecycle events). This means:

  • The new UIViewController automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers method must be implemented to return NO
  • You must declare and manage containment relationships using the new addChildViewController: and removeFromParentViewController methods of UIViewController

  • Conclusion

    This article has finally come to an end. It seems I have definitely pushed verbose mode too far this time, congratulations to those readers who could read this article in its entirety! I hope you now have a better understanding of view controllers, how they behave, and how to write containers which behave properly. There is so much to be said about view controllers, and I still have so much to discover about them, this article might only be the first in a long series. As always, comments and suggestions are welcome, and if you find errors please pinpoint them so I get fix them appropriately. Thanks in advance!

    Sample code

    The HLSContentContainer, HLSPlaceholderViewController, HLSStackController and HLSAnimation classes are available from the CoconutKit framework. Their behavior can be tested by building and running the CoconutKit-dev project.

    150 comments:

    1. Shouldn't one screen of views and subviews be managed by just one UIViewController?

      You should not use multiple custom view controllers to manage different portions of the same view hierarchy. Similarly, you should not use a single custom view controller object to manage multiple screens worth of content.

      http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/AboutViewControllers/AboutViewControllers.html#//apple_ref/doc/uid/TP40007457-CH112-SW12

      ReplyDelete
    2. "You should not use multiple custom view controllers to manage different portions of the same view hierarchy"

      Though the above sentence is part of the official Apple documentation, having multiple custom view controllers manage different portions of the same view hierarchy is what makes view controller containers possible. Consider for example the built-in UISplitViewController: It is a direct subclass of UIViewController (i.e. a custom view controller) managing two view controllers, each having its own view hierarchy. These two view hierarchies are part of the split view controller's view hierarchy itself. If the rule above could not be violated, the split view controller and built-in UIKit containers in general (UINavigationController, UITabBarController, e.g.) could never have been written.

      Implementing view controller containers is not an easy task (and I hope my article made it clear). I think that the above sentence should be interpreted as a warning that adding a view controller's view as subview of another view controller's view is dangerous. It can be done (and it must when you are writing a container), but it is difficult to get right and thus should be avoided.


      "Shouldn't one screen of views and subviews be managed by just one UIViewController?"

      The answer to your question is therefore "yes, in general": When writing an application, each screen (or subscreen) must be managed by a single view controller. Those view controllers are then combined using containers to build the required application worflow. You must never add a view controller's view as subview manually.

      But if you are a implementing a container, this piece of advice cannot be strictly followed. In this case, your responsibility is to be careful enough so that the container you write is well-behaved.

      ReplyDelete
      Replies
      1. The implementation is not right. The container should never manage child's view controller's view. It should only manage the child's view controller that manages its view hierarchy. Apple doc and WWDC very strongly state this management.

        Take look at the UISpliteViewController. It only manages its children view controllers but not its views.

        Delete
    3. Hi. Do you have some sample of a custom view controller inheriting from NSObject? I am new at iOS programming and I want to follow the good practices. I created a custom view controller that inherits from UIViewController and used a NIB file to design the view (I used the template from Xcode) but now I need to use it as a subcontroller of a normal controller (not a container), therefore I want to implement, if possible, the "view" outlet property with its lazy loading, etc. But if you think using a controller that inherits from UIViewController (and forwarding all the events to it) it is not much more costly, let me know please.

      Thanks! Excellent post.

      ReplyDelete
    4. I need to clarify some things about my previous request, because it will be probably interpreted as a case where the parent view controller should be a container (it's my fault). The subview controller I want to inherit from NSObject will be not a full controller really, it don't need to implement lifecycle events (except viewDidLoad) nor rotation events, also it will not contain any sub controller. I want this controller only to manage the loading of the view and to implement some custom "methods" related with its content, i.e. the parent controller will manage all the related with lifecycle events and rotation.

      Thanks

      ReplyDelete
    5. If you want to implement a custom object behaving like a controller and managing a view as UIViewController does, then why cannot you simply inherit from UIViewController? It is what UIViewController is made for, and trying to replicate what UIViewController does won't certainly be easy.

      But I am not sure I understand what you are trying to do. Could you give me some more details (nothing you do not want to talk about, of course) about your app workflow and your controller / view controller hierarchy?

      ReplyDelete
    6. Hi. Yes, it's because of that I tried to clarify things on my second comment. When I started the project I first created that view controller to implement a view with some subviews and some functionality used when rotating the iPad; I used it as the windows root controller. When it was finished, I proceeded to create the parent view controller: it displays a scroll view and uses instances of my first controller as pages (it's my first iOS project, I know it is not the best way to do the things probably). I don't want to implement a container, I still want to use my first controller as a mean to package the loading of the view and some especial methods (not a full controller really), managing the lifecycle events and rotation at the "parent" controller, and I want to follow the warnings about subcontrollers (where they always say I should use a customer view controller that inherits from NSObject but I can't find a good sample).

      (excuse my limited english :-) )

      ReplyDelete
    7. Ok, I see. What you need is a container managing a scroll view in which contained view controllers will be drawn. Should you inherit from NSObject (as you suggest) or from UIViewController? The fact that you do not want or need to implement the whole view lifecycle and rotation forwarding logic is not a good reason why you should not inherit from UIViewController. In fact, you can still have a container inherit from UIViewController and only implement the functionality you need. Such a container will be poor (poisonous in the sense of my article), but as long as it solves your problem, it is what matters after all.

      In your case, I would definitely inherit from UIViewController. After all, you need to manage a view (a scroll view, most probably), and UIViewController will do it for you.

      When I started writing containers, the first one I implemented did exactly what you want to do. The result was a poisonous container, which still works pretty well but is far from perfect. I plan to add it to the CoconutKit library, but not until it has been completely rewritten. I was keeping it hidden from view, but I just made it available on a separate branch if you want to have a look at it (https://github.com/defagos/CoconutKit/blob/feature/page-controller/CoconutKit/Sources/ViewControllers/HLSPageController.h).

      ReplyDelete
    8. Hi, I am using this in an iPhone project, but found that the first view in a container does not receive view*Appear events, is there any work around?

      Thanks!

      ReplyDelete
    9. Could you be more specific about your problem? Are you using HLSStackController or HLSPlaceholderViewController? Are the containers themselves embedded in some other view controller container, or displayed as root view controller of your application? Could you give me an overview of your view controller hierarchy? Thx

      ReplyDelete
    10. This was great! Thank you!

      One minor clarity correction: "I tend to name containers as HLSController if they ultimately inherit from UIViewController and if they are intended to be subclassed when used. In all other cases, I simply call containers HLSController."

      You use "HLSController" both times.

      ReplyDelete
    11. oops, blogspot ate the anglebrackets...

      ReplyDelete
    12. Thanks Dav, the mistake has now been fixed.

      ReplyDelete
    13. Is there any sample code which use the HLSPlaceholderViewController to create something similar to UISplitViewController? You mentioned it above but I can't find any in the CoconutKit-demo from the download package. Appreciate if you would post it here or provide some pointer to get the sample code for that. Many thnx.

      ReplyDelete
    14. The example from my article describes a pseudo split-view controller which contains only one child view controller (the table view on the left is not part of a dedicated view controller but belongs to the placeholder view controller's view itself). If this is all you need it is pretty easy to write a sample, let me know if you need one.

      There is currently no example of a true split-view controller (embedding two view controllers) in CoconutKit. In fact, a true split-view controller cannot currently be implemented using HLSPlaceholderViewController since it is meant to embed one child view controller only. You must use HLSContainerContent instead.

      I have started implementing an n-placeholder view controller (which, as the name suggests, can embed n view controllers) on a private development branch. I will then implement a split-view controller by inheriting from this new class with n = 2. This is on my todo list but I sadly cannot give you an exact time frame when this will be available.

      Regards.

      ReplyDelete
      Replies
      1. I really like to develop my own SplitViewController which as you said would contain n# of ViewControllers such as multiple TableViewControllers plus potential multiple content ViewControllers, maybe all contained inside a TabViewController. If you would kindly give me some hints on how to progress based on your existing class HLSPlaceholderViewController, and the things I need to pay attentions to, maybe we can co-work together to get this going? If you like, you can send me emails at sgsw.arthur@gmail.com regarding to this. Many thnx and look forward to hearing from you.

        Delete
      2. Instead of writing a container having n children, you should try splitting up your problem into smaller containers. In your case, I would write a custom tab view controller having one child view controller (this is really easy to achieve by subclassing HLSPlaceholderViewController), and a split view controller having 2 children (base your implementation on HLSContainerContent). I made everything possible so that HLSContainerContent allows you to relieve you from most container implementation issues. To give you an example, almost all applications I write for my company combine controllers from UIKit with HLSPlaceholderViewController and HLSStackController only.

        I will try to make some progress on my n-placeholder view controller and publish the associated branch publicly when it reaches a stable state. I don't have much time currently since I try to close some branches I have been working on for quite some time now, though. If you want to implement your own controller in the meantime, you should really have a look at HLSPlaceholderViewController implementation. This is the typical example of a container implementation based on HLSContainerContent. HLSStackController is interesting as well but more tricky. Basically, implementing an n-placeholder view controller inspired from HLSPlaceholderViewController implementation is quite easy: Add n as parameter to the container init method, replace the -placeholderView outlet with a -placeholderViews outlet collection, and where accessing -placeholderView loop over the outlet collection instead.

        Thanks for your interest! Best regards.

        Delete
      3. I updated the current HLSPlaceholderViewController implementation so that an arbitrary number of view controllers can be embedded. You might want to have a look at the commit details (https://github.com/defagos/CoconutKit/commit/d1cb69dfd5c0e0a54f7f9587bc0c248d9cf54917) for more implementation details. This will be available in CoconutKit 1.1.5.

        Best regards.

        Delete
      4. Many thnx for your quick reply, defagos. I have 2 questions if you don't mind to make sure I understand you correctly:

        1. When I study the source code, it seems to me that HLSContainerContent is private inside HLSPlaceholderViewController, and therefore it's kind of an implementation details and therefore the client of HLSPlaceholderViewController simply doesn't know anything about it at all. So, if I understand it correctly, then when you said "a split view controller having 2 children (base your implementation on HLSContainerContent)", you mean I define a subsclass of HLSPlaceholderViewController and in the implementation use 2 children of HLSContainerContent as private properties and 1 HLSContainerContent contains a UITableViewController, and another HLSContainerContent contains a HLSViewController, correct?

        2. What's the best way to communicate from the Child ViewController contained in HLSContainerContent back to the Container ViewController? For example, if I use a TableViewController as the child ViewController inside a subclass of HLSPlaceholderViewController, what's the best way to pass the selected row event back to the HLSPlaceholderViewController? It doesn't seem HLSPlaceholderViewControllerDelegate is the right delegate to use. Am I wrong? Would you please share some code snippets for this?

        Many thnx.

        Delete
      5. One more question:

        Who should implement HLSPlaceholderViewControllerDelegate? A Child ViewController? Therefore, it's mainly for HLSPlaceholderViewController to pass the ViewWillAppear, ViewDidAppear, animationWillStart, & animationDidStop events to the Child ViewController, right? If I need to pass other events to the Child ViewController, I have to define other delegates, right? (Pardon me if this is a dumb question).

        Delete
      6. Let me answer your questions:

        1. You are right, HLSContainerContent is an implementation detail of CoconutKit containers (HLSPlaceholderViewController and HLSStackController). The HLSContainerContent class is still public, though, so that anyone can reuse it to implement other view controller containers.

        When I say "a split view controller having 2 children (base your implementation on HLSContainerContent)", I mean you need to subclass UIViewController (or, better, HLSViewController) to implement a split-view controller, each child view controller being contained in an HLSContainerContent. But this is not needed anymore since HLSPlaceholderViewController now deals with an arbitrary number of child view controllers.

        2. I added methods to UIViewController so that a view controller can find whether it has been inserted into an HLSPlaceholderViewController (-[UIViewController placeholderViewController]) or an HLSStackController (-[UIViewController stackController]).

        If your view controllers are not meant to be reused, you can avoid the need to pass the selected row event back to the container: In your table view controller implementation, simply call self.placeholderViewController.insetViewController = viewControllerToDisplay to display a view controller when you click on a row.

        If you need further decoupling and reusability for your view controllers, you can declare a protocol through which child view controllers can communicate with the placeholder view controller they are in (which therefore acts as a kind of mediator). Simply have your placeholder view controller subclass implement the delegate protocols and respond accordingly.

        3. In general, I would make the object which instantiates the placeholder view controller implement its delegate protocol (this could be the application delegate, for example). This delegate protocol is not meant to be used to pass the view lifecycle events to child view controllers manually: This is entirely the job of HLSPlaceholderViewController, which does it transparently for you. If you need to pass more information between child view controllers, you can of course define additional protocols.

        Delete
      7. I got the new HLSPlaceholderViewController.h & HLSPlaceholderViewController.m from the branch. It's great. Would like to try it out. I'm setting my project using the Coconut binary staticframework. Not sure how to include your new source in the staticframework. Would you mind to give me some instructions? Appreciate your kindly help. This new code would be something great to try out and very helpful.

        Delete
      8. You cannot simply use the new source files with the existing .staticframework, it must be built again from scratch. I did it for your, you can download preview binaries from https://github.com/defagos/CoconutKit/downloads.

        Regards.

        Delete
      9. I know, I was trying to figure out if I can build it w/ the new source in the CoconutKit project. But not really sure the steps since I have never done it before. Is there any tutorial I can learn from to do that?

        Anyway, did manage to include the CoconutKit Project inside my own project and get the new HLSPlaceholderViewController working with 2 contained ViewControllers. Seems to be pretty straight forward. But it's still better to get the preview binary from you. Many thnx for your kindly help in such a short time frame. You are really great and helpful.

        Now need to really develop the SplitViewController. Need to figure out the easiest way to communicate among child view controllers. You mentioned above that using the PlaceHolderViewController as the mediator between child view controllers so that they would not be dependent on each other, seems to be a good idea and since each child controller already have reference to the PlaceholderViewController with the category you defined for UIViewController. Will work on it in next couple of days and let you know what happens.

        Again, many thnx for your good codes and kindly help.

        Delete
      10. The instructions to build CoconutKit are given in the readme markdown file (see "Building binaries"). All you need is the make-fmwk command (https://github.com/defagos/make-fmwk) which you must run from the /CoconutKit directory ("make-fmwk.sh -o /tmp -u 1.1.5-preview Release" to build the preview binaries, for example).

        I still intend to write a more advanced split-view controller for CoconutKit (most notably with resizable areas), but I hope that the current HLSPlaceholderViewController should suffice in your case. I tested it carefully but if you find any issues do not hesitate to open a dedicated ticket on github. Thanks!

        Delete
      11. 1.1.5-preview binary works good for the new PlaceHolderViewController. Looking forward to your advanced SplitViewController.

        Delete
    15. Dear Defagos:

      I got in some trouble by simply programmatically create a UIView and set it w/ the placeholderViews in loadview() of my subclass of the new HLSPlaceholderViewController. Somehow, I got in the infinite loop where after loadview(), it calls the parent hierarchy of viewDidLoad() and then when it reaches HLSViewController viewDidLoad(), somehow, it call my subclass's loadview() again! Below is the code snippet of my placeholderViewController subclass:


      - (id)init
      {
      if ((self = [super initWithNibName:[self className] bundle:nil])) {
      // Pre-load a view controller before display. Yep, this is possible!
      // self.insetViewController = [[[ChildTableViewController alloc] init] autorelease];
      ChildTableViewController *insetViewController = [[[ChildTableViewController alloc] init] autorelease];
      [self setInsetViewController:insetViewController atIndex:0];

      }
      inNavigationControllerSwitch = YES;
      inTabBarControllerSwitch = NO;
      return self;
      }


      - (void)loadView
      {
      UIView* placeholderChildView = [self createBoundViewforChild];
      self.placeholderViews = [[NSArray alloc] initWithObjects:placeholderChildView, nil];
      }

      - (UIView*) createBoundViewforChild
      {
      CGSize fullSize = [self splitViewSizeForOrientation:self.interfaceOrientation];
      float width = fullSize.width;
      float height = fullSize.height;

      if (YES) { // Just for debugging.
      NSLog(@"Target orientation is %@, dimensions will be %.0f x %.0f",
      [self nameOfInterfaceOrientation:self.interfaceOrientation], width, height);
      }
      CGRect childVCRect = CGRectMake(0, 0, width, height);
      childVCRect.size.width = m_splitPosition;
      UIView* placeHolderViewForChildVC = [[[UIView alloc] initWithFrame:childVCRect] autorelease];
      return placeHolderViewForChildVC;

      }


      Appreciate if you can help to find out the possible issue. If you want me to send you the complete source, please shot me an email at courageac@gmail.com. Thnx.

      ReplyDelete
    16. Trying to trace thru the source code and found this:
      During the calls to the hierarchy of viewDidLoad, in UIViewController+HLSExtensions.m's static void swizzled_UIViewController__viewDidLoad_Imp(UIViewController *self, SEL _cmd), it calls [self setOriginalViewSize:self.view.bounds.size] and this trigger my subclass's loadview().

      Was it that I did something I'm not supposed to do in my previous code quotes to cause this? I'm basically just follow what you said in the PlaceHolderViewController.h comments to instantiate placeholderViews in the loadview() of my PlaceHolderViewController subclass. Appreciate your help.

      ReplyDelete
      Replies
      1. There are at least two issues in your -[UIViewController loadView] implementation:

        1) You never assign a view to self.view. This is what -loadView is made for, and I suppose this is what leads to your recursion problem
        2) You create placeholder views, but you must still call -[UIView addSubview:] to add them to your view controller's view hierarchy

        Hope that helps.

        Delete
      2. Yes, many thnx. Got it working along w/ Story board. The beauty of this PlaceHolderVC is that it's not really just limited to Master & Detail. It can contain more complicated set of VCs interacting w/ each other. And even best is that it is not limited to be just as the RootVC as UISPlitVC does. Therefore, can really use it along w/ Storyboard.

        I guess still have quite a bit of work to do to get it working w/ CoreData just like that of the standard SplitVC.

        Delete
      3. Dear Defagos:

        I'm trying to implement a moving divider between the Master & Detail. Just want to verify my understanding of best way to do it. I suppose I can reuse the existing Master & Detail VC (View Controllers) without calling HLSPlaceHolderViewController's setInsetViewController() again. I just need to create a new array of UIViews with new dimensions and set it to placeholderViews. And then call the HLSPlaceHolderViewController's viewWillAppear() to update the display the Master & Detail VC again.

        Many thnx.

        Delete
      4. 1) This was one of the primary requirements of PlaceholderVC and StackController. By being able to combine them easily (and even with standard UIKit containers) or use them as root view controller of an application, you can easily create complicated view controller hierarchies you can later easily refactor if needed. For example, the applications I am currently working on nests up to 5 of those containers.

        CoconutKit also provides several tools for working with CoreData easily (HLSModelManager, text field bindings). You might want to look at them to save you some time and headaches.

        2) I did not try implementing a split VC using HLSPlaceholderVC yet, but here is probably how you should do it: Subclass HLSPlaceholderVC, create two placeholder views programmatically in -loadView, and add a slider to resize the placeholder views when moved. Since the placeholder views are setting the autoresizing mask of the view controller's views which are drawn on them (see -[HLSContainerContent addViewToContainerView:inContainerContentStack:] implementation), the behavior should be correct, provided the child view controllers are configured propertly.

        You must never call viewWillAppear: to update the display, it is not what this method is made for. If you need to force a layout, use UIView setNeedsLayout or layoutIfNeeded methods

        Delete
      5. Just want to let you know that I got the SplitViewController based on HLSPlaceHolderViewController almost all working except encountering some problem w/ putting the NavigationVC embedded MasterViewController inside the Popover in portrait mode. For some unknown reasons, it throw exceptions during the instantiation of the UIPopover class. I can move the split divider between the Master & Detail Views and also incorporate it with Core Data for the Master VC. Many thanks for all of your kindly help, Mr. Defagos.

        Delete
    17. Dear Defagos:

      Do you think if you would have a ARC-enabled version of the CoconutKit very soon?

      ReplyDelete
      Replies
      1. Since CoconutKit can already be used with ARC projects, you are probably talking about the source code itself. No, I do not currently plan to update CoconutKit to ARC, and for several reasons:

        1) The code base is large, and it would require a lot of time and testing to carefully migrate to ARC. I prefer adding up new components or improving existing ones instead

        2) I use properties for memory management (see http://subjective-objective-c.blogspot.ch/2011/04/use-objective-c-properties-to-manage.html), which almost completely eliminates the need to send retain and release messages manually (to give you an idea, the code of CoconutKit and all demos currently only contains 16 retains and 21 releases!)

        3) I must also admit that I am still not really fond of ARC yet. At its core, manual memory management is just retain, release, and Objective-C conventions. With ARC, there is a large number of rules to know (http://clang.llvm.org/docs/AutomaticReferenceCounting.html), and new innovative ways to create subtle bugs (http://weblog.bignerdranch.com/296-arc-gotcha-unexpectedly-short-lifetimes/). IMHO, good programming languages are those you can forget so that you can focus on what's important: Solving problems. All my recent projects use ARC, and I now spend more time thinking about language issues and debugging subtle leaks than ever before.

        Delete
      2. Yes, got it working w/ my project w/ ARC yesterday. And thanks for your kind sharing of your experience. Couldn't agree more.

        Delete
    18. Thanks for this post. I enjoyed it! More business people and product managers should read articles like this.

      ReplyDelete
      Replies
      1. Thanks for your feedback! The situation has changed somewhat with the releases of iOS 5 and now iOS 6, though (new methods have appeared, as well as the new iOS containment API). Most of the information from this article remains valid, but some information is now clearly missing. However, I took care of implementing containers carefully once for all, taking into account the newest features made available in the latest iOS versions, so that nobody should ever need to roll its own view controller containers. This will all be made available later this month in CoconutKit 2.0 (https://github.com/defagos/CoconutKit)

        Delete
    19. Great article I must admit. Why don't you collect your expertise and write a book. Articles like this should be reference to each ios programmer from the beginning.
      Thank you.

      ReplyDelete
      Replies
      1. Thank you very much. I really would like to write some kind of book, but I sadly miss time. It took me so much effort to study and implement containers correctly I did not find enough time to write about them. But honestly, a year later after this article has been written, I now probably would not write that much about the subject anyway.

        When writing view controllers, you namely only need to keep a few things in mind (the view lifecycle, how rotation works and when frames are reliable, most notably). This is really the kind of stuff which an article should make clear, nothing more (after all, this is what most people need). This information is scattered around my article, but is "polluted" with container-specific stuff which most iOS programmers should never have to worry about if Apple provided more containers, or a better API to write them.

        Therefore, instead of writing a reference book or many articles which will probably become obsolete quite fast (and especially since iOS 5 and iOS 6 changed so much about view controllers), I decided to write a reference implementation which all iOS developers will be able to use, avoiding the need to write their own containers. This is of course not a book, but if you are interested in the subject you can have a look at the source code of CoconutKit containers, which contains much more information than the article above, or than any other article I may write.

        Best regards.

        Delete
    20. I am trying to hide part of my UI in landscape orientation. The UI deteriorates after some turnings of the device. I must have done something fundamentally wrong...
      How would you use CoconutKit to make this work?

      My UI-destructing code is on GitHub: https://github.com/AOphagen/RotationAnimationError/blob/master/RotationAnimationError/MainPlaceholderViewController.m

      So far, CoconutKit has been one fine tool for me, thank you.

      ReplyDelete
      Replies
      1. I had a look at your code (though this blog is probably not the right place to talk about it). CoconutKit containers won't make your toolbar animation just work, they only guarantee that, provided you insert view controllers into them, everything will just work as expected (lifecycle, rotation, etc.). But in your case your problem seems to be the toolbar itself, not the placeholder view.

        I cannot pinpoint what goes wrong with your code. IMHO, what you are trying to do should be quite simple, and the code required should be much shorter than what you currently have (basically, resize change the placeholder view height, and move the toolbar down during the rotation). One HLSAnimation should suffice. You can simply play it backwards (use -[HLSAnimation reverseAnimation]) when rotating back from landscape to portrait

        Regards.

        Delete
      2. Thank you very much! Thinking around too many corners is a usual problem of mine. Working with one animation and its reverse, I should change the point in the rotation process when I animate back, I think. Then I would need to show the landscape version of the toolbar before rotation, wouldn't I? And let Apple animate the landscape version to portrait version part? Happily going back to try it... :D

        Delete
      3. Thank you once more. It is working just fine now. I had to change a lot. There were two tricky things: 1) the device rotation from one landscape to the other via upside down caused the animation to run twice - I added a flag to control that. 2) the precise moment of my animation (before or after call to super) was very important - I just tried different positions until all was to my liking. I have synced the working code into github. I am SO happy! On to the next puzzle :D

        Delete
    21. Do you need some help in writing? 1st writing service you'll get an opportunity to receive a well written paper!

      ReplyDelete
    22. Hi, This is a good post, indeed a great job. You must have done good research for the work, i appreciate your efforts.. Looking for more updates from your side. Thanks

      Quality Home Builder in Oak Forest
      Oak Forest Spec House Builderr

      ReplyDelete
    23. if you want to pop up your website then you need office 365 download

      ReplyDelete
    24. This comment has been removed by the author.

      ReplyDelete

    25. Leolist Charlottetown


      Are you Searching for Leolist Charlottetown? We have the best alternative of Leolist Charlottetown here on https://charlottetown.xgirl.ca/
      Visit https://charlottetown.xgirl.ca/ today and find the best results related to Leolist Charlottetown.

      ReplyDelete
    26. I am in fact thankful to the owner of this web site who has shared this great piece of writing at here.|
      바카라

      ReplyDelete
    27. If you are going for most excellent contents like me, simply go to see this site every day for the reason that it presents quality contents, thanks
      파워볼게임

      ReplyDelete
    28. Full-stack development refers to developing both front-end (client-side) and back-end (server-side) portions of a web application. You can hire full stack developer depending on your company’s needs.

      Full-stack web Developers: Full-stack web developers can design complete web applications and websites. They work on the front-end, back-end, database, and debugging of websites.

      ReplyDelete
    29. I know this website presents quality depending articles or reviews and other stuff, is there any other site which gives these kinds of data in quality? 바카라사이트

      ReplyDelete
    30. For example, buy 15 free spins slots. But in the free spins game 40 free spins are awarded, players will instantly receive a total of 55 free spins, this is how they can claim their winnings. ตารางคะแนน

      ReplyDelete
    31. itsallgame.com is a superslot website that collects online slots games. Many brands, whether live22 สล็อต

      ReplyDelete
    32. PG Slot, fun to play with all systems no download required Playable through the website ข่าวบอลวันนี้

      ReplyDelete
    33. Choose the game as Choosing a game that is suitable for the player himself It is considered something that should be said that it should be very much. ambbet

      ReplyDelete
    34. in any direction. By retraining the muscles you begin from a relaxed position, giving a quickened reaction time. pgslot

      ReplyDelete
    35. BREAKING NEWS PLEASE CHECK IN MY WEBSITE FOR DETAIL INFORMATION GUYS. PENCARI CUAN123

      BREAKING NEWS PLEASE CHECK IN MY WEBSITE FOR DETAIL INFORMATION GUYS. PENCARI CUAN123

      BREAKING NEWS PLEASE CHECK IN MY WEBSITE FOR DETAIL INFORMATION GUYS SO WHAT ARE YOU WAITING FOR SOLUSI CUAN123

      BREAKING NEWS PLEASE CHECK IN MY WEBSITE FOR DETAIL INFORMATION GUYS SO WHAT ARE YOU WAITING FOR SOLUSI CUAN123

      PLEASE HELP ME TO CLICK MY SECOND AMAZING WEBSITE GUYS I HAVE A LOT GAME AND YOU CAN PLAY IT CUANTOGEL123

      PLEASE HELP ME TO CLICK MY SECOND AMAZING WEBSITE GUYS I HAVE A LOT GAME AND YOU CAN PLAY IT CUAN123

      PLEASE HELP ME TO CLICK MY SECOND AMAZING WEBSITE GUYS I HAVE A LOT GAME AND YOU CAN PLAY IT CUAN123

      ReplyDelete
    36. Ready to deposit money through the automatic system Versatility in every bet that anyone can play and make real money. pgslot เว็บตรง

      ReplyDelete
    37. Fruit juices that are high in vitamin C include: Poly kale and fruits that are high in vitamin C are citrus fruits. pgslot

      ReplyDelete
    38. This blog is really helpful for the public .easily understand,
      Thanks for published,hoping to see more high quality article like this.
      온라인바카라

      ReplyDelete
    39. Not through middlemen, bonuses, full set up and easy to play with Thai menu Welcome pgslot เว็บตรง

      ReplyDelete
    40. Our ABMgiant website has been around for a long time. There are more than ten thousand people using the service per day. There is an identity. Customers can understand the service 24 h ตารางคะแนนพรีเมียร์ลีก

      ReplyDelete
    41. Make financial moves that are both fun and exciting while playing online games. Welcome ข่าวบอลล่าสุด

      ReplyDelete
    42. Our web service provider will improve the system even further. With over 10 years of experience, we will definitely not disappoint our players. pgslot เว็บตรง

      ReplyDelete
    43. Unlimited work options This can be done via mobile phone or computer. Remuneration starting from 0.1 baht/job and above. Withdrawals can be made once you have accumulated 100 baht. ตารางคะแนนพรีเมียร์ลีก

      ReplyDelete
    44. That giant, the only place to choose to play online slots games with a reliable website You will play online slots. at ease don't be afraid of being cheated Play pgslot เว็บตรง

      ReplyDelete
    45. Fish shooting game. If talking about this game, no one will know for sure. especially the players สล็อต

      ReplyDelete
    46. Our experts who offer MYOB Assignment Help are cognizant of the significance of accountancy assignments and extend a helping hand to students in order to help them manage the complex of assignments; develop a solid understanding of the topic. They assist you with doing your work and developing a solid base in the subject so you may stand out from your classmates and fellow students. Our online site, which includes Online Assignment Help, is credible and genuine.

      ReplyDelete
    47. Available to players that should not be missed very much. If talking about the best website that allows players to use the service in a comprehensive way sexybacarat

      ReplyDelete
    48. Online games with more than 10 years of experience, we will not disappoint players for sure. superslot

      ReplyDelete
    49. Online slots are a type of gambling game. with a pattern to play That is very easy to play and has also been สมัครสมาชิกสล็อต pg

      ReplyDelete
    50. Able to apply to start playing very easily. It only takes less than 5 minutes to start playing. ข่าวบอล

      ReplyDelete
    51. The center of online slots web that offers online games for all camps. The most popular and hottest right now pgslot

      ReplyDelete
    52. systems and here and if players is looking for a website to play online games that have bonuses that are easy to break, often broken, get pgslot เว็บตรง

      ReplyDelete
    53. quite diverse We have various helpers. which will allow you to Easy to win at slot games pgslot

      ReplyDelete
    54. In addition, PG Slot has a variety of game themes to choose from and many nationalities, such as Muay Thai themes, Aladdin themes, Suki pot themes Who would have thought that slots ข่าวบอลล่าสุด

      ReplyDelete
    55. This is a great inspiring article.I am pretty much pleased with your good work. You put really very helpful information. Keep it up. Keep blogging. Looking to reading your next post.영양출장샵추천
      청도출장샵추천
      고령출장샵추천
      성주출장샵추천
      예천출장샵추천
      봉화출장샵추천

      ReplyDelete
    56. In other words, over the course of your lifetime, you are likely to pay far less for your college education than you would pay (in e pgslot

      ReplyDelete
    57. More takebacks If your profits are underestimated, your investments will not be sustainable. pgslot เว็บตรง

      ReplyDelete
    58. Microservices architecture and agile development are key as part of a successful digital transformation strategy.

      ReplyDelete
    59. 985pgslot online slots site that will keep you entertained and enjoy playing slots Because you can play anywhere, anytime, just with a mobile phone. jili slot

      ReplyDelete
    60. You don't have to register for multiple users, but you have all the games to play. hilo Deposit 1 get 30 In addition to Slot Online games, now also launching Casino Online pgslot เว็บตรง

      ReplyDelete
    61. The old fashion way in sending mails had been thrown out in some people. Mostly now uses E-MAIL for ambbet

      ReplyDelete
    62. Make real money, no cheating, ready to have fun. Let's just say that today we have compiled a game. pgslot เว็บตรง

      ReplyDelete
    63. with its own uniqueness including online slot gameswith its own uniqueness including online slot games slotxo

      ReplyDelete
    64. With just having internet or WIFI, you can play anytime, anywhere, deposit and withdraw by yourself with automatic system. สล็อต

      ReplyDelete
    65. Choose from a variety of online games, XO slots and online games. that are other games And also play that game in style. pgslot

      ReplyDelete
    66. Ready to take care of you 24 hours a day. You can contact them directly. There are staff to take care of and provide information all the time or even other services pgslot เว็บตรง

      ReplyDelete
    67. All the time, don't worry because we have international service. Players can access the service through the joker login to register as a member with our website first. pgslot

      ReplyDelete
    68. protect yourself This is normal for wealthy families. which can be bought Naturally, samurai and ninja clans They were vying for that position. ดูบอลออนไลน์

      ReplyDelete
    69. prizes that are ready to come out at any time. Let me tell you that this is another game that earns good money, a lot of profits and not a lot of investment. pgslot

      ReplyDelete
    70. SuperSlot, which has a wide variety of games ever. for various online gambling games and more importantly pgslot เว็บตรง

      ReplyDelete
    71. The team is waiting for advice to solve all the rules of casino games in the world. Easy to apply in a few steps. ready to receive countless privileges that will follow pgslot เว็บตรง

      ReplyDelete
    72. automated system that can deposit - withdraw quickly. In addition, free credits are always given pgslot เว็บตรง

      ReplyDelete
    73. Direct website, new update 2022, hot, stick to the charts, bet for real money welcome AMBKING

      ReplyDelete
    74. Drink coffee in moderation. Should not drink too much and cause insomnia. And it's not just coffee. pgslot เว็บตรง

      ReplyDelete
    75. Betting websites where you can make money and earn more returns than other websites. If talking about pgslot เว็บตรง

      ReplyDelete
    76. Free credit giveaway activities, which will be organized in rounds. pgslot

      ReplyDelete
    77. proof of transfer is required. Because you can deposit and withdraw by yourself. It is quite the most modern and good deposit and withdrawal system. ผลบอลสด

      ReplyDelete
    78. Never played online slots games before, can come to use the service and make money pgslot

      ReplyDelete
    79. The process of applying for membership, playing, shooting fish, deposit-withdrawal, no minimum, no hassle. pgslot

      ReplyDelete
    80. Slots, online games, slots on mobile, top-up-withdraw via automatic system
      24 hours service, 100% safe and secure. pgslot

      ReplyDelete
    81. Can play anytime, anywhere because our website is modern Take care of you thoroughly, no matter where you are. AMBKING

      ReplyDelete
    82. The entrance to joker gaming is modern, convenient and comfortable.
      which gambler pgslot

      ReplyDelete
    83. This comment has been removed by the author.

      ReplyDelete
    84. Because slots are easy games to play. can do extra career And it is also a game that bettors ดูบอลสด

      ReplyDelete
    85. Lucky Piggy comes in the form of a 3D slot game with an amusement park in a bright city. pgslot

      ReplyDelete
    86. joker123 online slot game Open for service 24 hours a day, deposit-withdrawal, secure, 100% safe ดูบอลสด

      ReplyDelete
    87. Thanks for taking the time to discuss that, I'd love to learn more about this area. Would you mind updating your blog post with additional insights? It will be really helpful for all of us.
      girls games

      ReplyDelete
    88. The siren song of "Buy YouTube Subscribers" can be tempting, promising instant gratification and inflated numbers. But like a mirage in the desert, it evaporates upon closer inspection, leaving your channel parched and ultimately unsustainable. The true oasis lies in building a genuine audience, a loyal community nurtured by the lifeblood of high-quality content and genuine engagement.This journey requires dedication, not a quick buy-button fix. It's about delving into the heart of your niche, unearthing their passions and pain points. It's about research, understanding their online language, and crafting videos that resonate deep within their souls. Imagine yourself as a cartographer, meticulously mapping the landscape of your audience's needs and desires.
      https://www.buyyoutubesubscribers.in/

      ReplyDelete
    89. Delhi is a renowned hub for LASIK eye surgery, an effective solution for vision correction. Expert ophthalmologists leverage the latest technology to precisely reshape the cornea in a swift, 15-minute procedure per eye. The surgery is non-invasive and painless, making it a popular choice for many. Rapid recovery and vision improvement within 24 hours, coupled with Delhi's accessibility and cost-effectiveness, make this metropolis a preferred destination for those seeking a life free from glasses or contact lenses.
      https://www.visualaidscentre.com/lasik-eye-surgery/

      ReplyDelete
    90. Boost the visibility of your YouTube channel with our Subscriber Service. Offering a safe and efficient way to increase your subscriber count, we ensure a growth that is just as substantial as it is authentic. With real subscribers expressing genuine interest in your content, we enhance the reach and engagement on your channel. Trust us to transform your YouTube channel into a popular platform, propelling you to success in one of the most dominant social media landscapes.
      https://sites.google.com/view/buyytsubscribers/

      ReplyDelete
    91. Buying YouTube views is akin to stepping onto a double-edged sword; it offers the temptation of instant visibility but comes with inherent risks. This strategy doesn't foster a loyal audience but inflates metrics artificially, a mirage of popularity that can vanish as swiftly as it appears. It's a gamble, potentially boosting a video's algorithmic appeal at the expense of genuine engagement and audience trust. As creators dabble with this approach, they must tread carefully to avoid crossing the lines of YouTube's stringent policies, lest their ephemeral gains culminate in lasting damage to their hard-earned reputation.
      https://www.buyyoutubeviewsindia.in/

      ReplyDelete
    92. Economical web hosting companies in India are tailor-made for up-and-coming businesses and personal projects aiming for an online presence. By adopting shared hosting methods, these providers manage to offer attractive prices, spreading the operational costs across various users. Key hosting elements, including a set amount of disk space, bandwidth, and email functionality, are offered to keep new websites running efficiently. Clients benefit from value-added services such as 24/7 support and easy integrations while weighing the potential compromises in website performance and uptime for the sake of affordability.
      https://onohosting.com/

      ReplyDelete
    93. Acquiring YouTube subscribers for a quick numerical uptick poses ethical questions and jeopardizes the channel's integrity. It can lead to a disengaged audience, lack of authentic interactions, and might breach YouTube's terms, risking penalties or even channel suspension, undermining any short-lived gains.
      https://buyyoutubesubscribersindia.weebly.com/

      ReplyDelete
    94. Acquiring Google reviews through purchase is inherently risky and undermines the core values of transparency and trust between consumers and businesses. It presents an unethical shortcut in an attempt to fabricate a positive public perception, thereby potentially deceating potential customers. True credibility and customer loyalty are built upon honest assessments of service and experience. Over time, this deceptive practice can tarnish a business's reputation, leading to negative consequences that could outweigh any short-term gains. It is imperative for businesses to seek genuine evaluations to maintain integrity and foster growth based on actual customer satisfaction and feedback.
      https://www.seoexpertindelhi.in/buy-google-reviews/

      ReplyDelete

    95. Looking to boost your Spotify presence? Buy Spotify monthly listeners and watch your music reach new heights. With a steady influx of listeners, your tracks will gain visibility and credibility. Increase your chances of getting discovered by potential fans and industry professionals. Stand out from the crowd and amplify your musical career with this strategic investment. Don't let your talent go unnoticed – invest in monthly listeners today! Elevate your profile and attract attention from record labels and music influencers. Take control of your success and watch your audience grow organically. Gain a competitive edge in the ever-evolving music industry landscape. Invest in your music's future and unlock endless opportunities for growth and recognition. Ready to take your Spotify game to the next level? Buy monthly listeners now and see the difference it makes!
      https://www.spotifyfame.com/

      ReplyDelete
    96. Looking to boost your Twitch channel's visibility? Consider purchasing Twitch viewers to give your streams a competitive edge. With increased viewership, your content gains credibility and attracts more organic viewers. Buying Twitch viewers can help your channel stand out in the crowded streaming landscape. Don't miss out on potential followers – invest in Twitch viewers today! Enhance your channel's reputation and increase your chances of being discovered by new audiences. Gain a competitive advantage and watch your stream climb the ranks. Take control of your streaming journey and unlock new opportunities for success. Ready to make a significant impact? Purchase Twitch viewers now and elevate your streaming career!
      https://twitchviral.com/

      ReplyDelete
    97. Looking to boost your YouTube presence? Consider buying YouTube views to increase the visibility of your videos. With more views, your content gains credibility and attracts a larger audience. Buying YouTube views can help your videos stand out in the competitive online landscape. Don't miss out on potential viewers – invest in views today! Enhance your video's reputation and increase your chances of being recommended by the YouTube algorithm. Gain a competitive edge and watch your channel grow in popularity. Take control of your content's success and unlock new opportunities for growth. Ready to make a significant impact? Purchase YouTube views now and see your channel thrive!
      https://sites.google.com/site/buyytviewindia/

      ReplyDelete
    98. Discover affordable web hosting with India’s top providers, offering cost-effective solutions without sacrificing quality or performance. Get the best possible value with plans that include free SSL certificates, unlimited bandwidth, and 24/7 support. Benefit from high uptime rates, ensuring your website is always accessible to visitors. Leverage user-friendly control panels for effortless website management. Opt for scalable hosting services that grow with your business, from shared hosting for startups to VPS for expanding enterprises. With data centers located in India, experience faster load times and improved SEO rankings. Choose from a variety of plans to suit your budget, and take advantage of easy one-click installations of popular CMS platforms like WordPress. Secure your digital presence with reliable and inexpensive web hosting tailored for the Indian market.
      https://hostinglelo.in/

      ReplyDelete
    99. Tap into the power of positive feedback with bulk Google reviews in India, specifically crafted to enhance your business's online reputation. Bolster your credibility with a multitude of authentic, diverse testimonials, reflecting the quality of your service or product. Purchasing these reviews in bulk allows for a cost-effective approach to bolstering your brand image and SEO ratings. An increased number of reviews can lead to higher search rankings, driving organic traffic to your website. Benefit from a wider reach across the Indian market, gaining trust with every potential customer who reads about the positive experiences others have had. Invest in bulk Google reviews and watch as your reputation, trustworthiness, and customer base grow in synchronization.
      https://www.sandeepmehta.co.in/buy-google-reviews/

      ReplyDelete
    100. Nursing jobs in Singapore represent a fusion of compassion, skill, and innovation within a multicultural healthcare setting. Renowned for its sophisticated medical infrastructure, the island-state offers nurses a platform to engage with advanced healthcare technologies and methodologies. Professionals in this field enjoy a clear pathway for career advancement, supported by a government committed to maintaining high standards in the health sector. They operate in a culture that respects their vital role, ensures competitive compensation, and promotes work-life balance. Singapore's health system continues to set international benchmarks, inviting nurses to contribute to a legacy of excellence in patient care.
      https://dynamichealthstaff.com/nursing-jobs-in-singapore-for-indian-nurses

      ReplyDelete
    101. When seeking a top-notch breast specialist in Mumbai, patients have access to a prominent female doctor with a distinguished track record in providing exceptional care. Specializing in both non-surgical and surgical treatments, she offers comprehensive services for a variety of breast-related conditions, including cancer, benign tumors, and cosmetic breast surgery. Her approach combines cutting-edge medical techniques with a compassionate understanding of her patients' concerns and desires. With a focus on personalized treatment plans, she ensures that each patient receives care tailored to their specific needs. Her expertise, coupled with a dedicated team, provides a supportive and professional environment for all seeking breast health services in Mumbai.
      https://drnitanair.com/

      ReplyDelete
    102. In Ahmedabad, a colorectal cancer specialist is making significant strides in the field of gastroenterology and oncology. Known for their exceptional diagnostic prowess and surgical expertise, this doctor is a pillar of hope for many facing this challenging condition. Utilizing the latest in medical technology and evidence-based practices, they craft personalized treatment plans that address both the physical and emotional aspects of cancer care. Their clinic, a hub of innovation and compassion, offers a supportive environment where patients and their families can find comprehensive care. Beyond the clinic, this specialist is actively involved in community health initiatives, aiming to raise awareness about colorectal cancer and promote early screening. With a commitment to excellence and patient care, this specialist stands out as a guiding light in Ahmedabad's medical community, changing the narrative of colorectal cancer treatment through dedication and pioneering approaches.
      https://drvirajlavingia.com/

      ReplyDelete
    103. This comment has been removed by the author.

      ReplyDelete
    104. The Breast Specialist Doctor in Gurgaon is revered for their unparalleled dedication to combating breast cancer through cutting-edge treatments and compassionate care. Employing the newest advances in medical technology, they create personalized treatment plans that reflect the unique needs and circumstances of each patient. At their clinic, patients find not only expert medical care but also a supportive and understanding environment that addresses both their physical and emotional well-being. This specialist goes beyond the clinical role to act as an educator and advocate, vigorously promoting breast health awareness and the critical importance of early detection through community engagement and public seminars. Their holistic approach to treatment, combined with their tireless dedication to patient advocacy and education, positions them as a leading figure in breast oncology in Gurgaon.
      https://www.breastoncosurgery.com/

      ReplyDelete
    105. Looking to boost your music's visibility on Spotify? Buying Spotify monthly listeners could be the game-changer you need! With an influx of listeners, your tracks gain traction faster, attracting more organic listeners and potentially landing on popular playlists. Investing in this service not only enhances your visibility but also signals credibility to potential fans and industry professionals. Plus, it's a convenient way to jumpstart your music career without waiting for slow, organic growth. Remember to choose a reputable provider to ensure genuine listeners and avoid any risks of bot-generated plays. So, why wait? Take control of your Spotify presence today and watch your music soar to new heights with purchased monthly listeners!
      https://www.spotifyfame.com/

      ReplyDelete
    106. Dive into the epitome of sophistication and surgical finesse with Silk Surgery in India. Picture a transformative experience where the rich cultural tapestry of India meets cutting-edge surgical techniques, resulting in an exquisite blend of tradition and innovation. Embark on a journey where each delicate stitch symbolizes precision, and the silk touch represents the seamless harmony of ancient wisdom and modern expertise. In the heart of India, your transformation unfolds – a silk surgery that not only redefines beauty but also embraces the diverse allure of this vibrant nation. Indulge in the luxury of Silk Surgery, where every thread weaves a story of aesthetic refinement, leaving you with a masterpiece that reflects the beauty of India itself.
      https://medium.com/@pojagupta/silk-eye-surgery-elita-860c70c593ad/

      ReplyDelete
    107. Discover the magic of radiant eyes with Smile Pro Eye Treatment! Just like a smile lights up a room, this innovative treatment illuminates your eyes, banishing tiredness and stress. Imagine a world where your eyes reflect the joy within, where fine lines fade into oblivion, and dark circles surrender to a luminous glow. With Smile Pro, your eyes aren't just windows to your soul; they're beacons of confidence and vitality. Embrace the sparkle, indulge in the rejuvenation, and let your eyes shine brighter than ever before!
      https://smileproeyesurgery.medium.com/smile-pro-eye-surgery-in-delhi-fbafc721a9ce/

      ReplyDelete
    108. Celebrate excellence with our Awards Trophies Supplier, where quality meets prestige. Elevate your events with bespoke trophies that embody craftsmanship and sophistication. Our range caters to diverse occasions, from corporate achievements to sporting triumphs. As your trusted Awards Trophies Supplier, we offer a vast selection of designs to suit every accolade. Imprint your brand or event with a touch of distinction, ensuring that the recognition lasts a lifetime. Choose excellence, choose us – your premier Awards Trophies Supplier, dedicated to making your victories unforgettable.
      https://www.angelstrophies.com/

      ReplyDelete
    109. Navigate the complexities of matrimonial law with confidence, guided by the expertise of the Best Matrimonial Lawyer in Delhi. Our seasoned professionals bring unparalleled legal acumen to safeguard your rights and interests. Specializing in delicate matters, we offer compassionate support during challenging times. As the premier choice for matrimonial cases in Delhi, our commitment is to ensure a smooth legal process tailored to your unique situation. Trust in the experience of the Best Matrimonial Lawyer in Delhi for strategic counsel and dedicated representation. Let us be your advocate, providing a steadfast legal foundation for a brighter future.
      https://bestdivorcelawyerindelhi.com/

      ReplyDelete
    110. Explore exciting opportunities in the UK with Nurse Jobs, and the best part – no IELTS required! Open doors to a fulfilling career without the language barrier. Join a dynamic healthcare team and contribute to the UK's renowned medical sector. Our streamlined hiring process values your expertise, making it easier for qualified nurses to apply and thrive. Unlock your potential with Nurse Jobs in the UK, where your skills are valued over language proficiency. Seize the chance to build a rewarding career and embark on a journey of professional growth, all without the need for IELTS. Your dream nursing job awaits in the UK – apply today!
      https://suntechhealthcarepro.com/nursing-jobs-in-uk-without-ielts/

      ReplyDelete
    111. Boost your channel's reach instantly,
      Buy YouTube Subscribers Fast, see
      Your content soar, your audience grow,
      Engage and captivate, let it flow.
      Gain credibility, climb to the top,
      With subscribers bought, your channel won't stop.
      Unlock success, seize the opportunity,
      Buy YouTube Subscribers Fast, and witness unity.
      https://www.deccanherald.com/brandspot/pr-spot/buy-youtube-views-subscribers-the-company-that-is-churning-successful-youtube-stories-one-after-another-1118911.html/



      ReplyDelete
    112. Elevate your YouTube channel with authentic growth – buy non-drop YouTube subscribers today! Our service ensures lasting engagement and genuine audience expansion. Say goodbye to fluctuating subscriber counts and hello to steady growth. Enhance your channel's credibility and visibility with our reliable subscription packages. Gain subscribers who stick around and interact with your content consistently. Don't settle for temporary boosts – invest in sustainable growth for your channel. Purchase non-drop YouTube subscribers now and watch your audience flourish!
      https://www.theweek.in/news/biz-tech/2022/07/07/buy-youtube-views-and-grow-your-business-in-2022.html/

      ReplyDelete
    113. Ready to take your YouTube channel to the next level? Buy Genuine YouTube Subscribers and watch your audience grow authentically. Our service ensures real subscribers who engage with your content. Say goodbye to fake accounts and hello to genuine support. Boost your channel's credibility and attract more viewers organically. Invest in quality subscribers and see your channel thrive. Don't settle for shortcuts – choose authenticity and watch your influence soar. Purchase Genuine YouTube Subscribers today and unlock your channel's full potential!
      https://www.aninews.in/news/business/business/viral-promotions-the-company-that-has-created-stars-out-of-youtubers20230328182117/

      ReplyDelete
    114. Our Australian SEO agency is a leader in delivering top-tier search engine optimization services. Harnessing a blend of strategic insights, market expertise, and cutting-edge technology, we specialize in elevating businesses across the digital landscape. Each campaign is crafted with precision to meet your specific goals, focusing on driving relevant traffic, enhancing online visibility, and converting visitors into loyal customers. With a passionate team of SEO experts dedicated to your success, we promise a partnership that transcends traditional boundaries, ensuring your brand not only achieves but exceeds its digital marketing objectives. Choose us for a bespoke SEO strategy that sets your business apart in Australia's competitive online arena.
      https://digiquakesolutions.com/

      ReplyDelete