I'm displaying a UITabBar in my app, and am trying to assign accessibilityIdentifiers to the buttons. To accomplish this, I use the following lines in each of my view controller instantiations:
viewController.tabBarItem.accessibilityIdentifier = #"ViewControllerID";
These viewControllers all get added to the UITabBar like so:
NSMutableArray *tabBarItems = [NSMutableArray array];
for (NSInteger i=0; i<_viewControllers.count; i++) {
UIViewController *viewController = [_viewControllers objectAtIndex:i];
[viewController setCommonTabBarController:self];
[self addChildViewController:viewController];
if (i == 0) {
viewController.view.frame = self.currentTabView.bounds;
[self.currentTabView addSubview:viewController.view];
[self addConstraintsToSubView:viewController.view];
_selectedViewController = viewController;
_selectedIndex = 0;
}
[viewController didMoveToParentViewController:self];
[tabBarItems addObject:[viewController tabBarItem]];
}
[self.tabBar setItems:tabBarItems animated:animated];
So, what I think should be happening here, is we are grabbing the tabBarItem that has the accessibilityIdentifier set correctly (when I set breakpoints, the accessibilityIdentifier of each view controller is what I expect.) Then, when it is actually displayed, there is no accessibilityIdentifier.
Things I've noticed:
iOS is using UITabBarButton instead of UITabBarItem. I think that has something to do with it. When I print out the items array of the tab bar, each of the items has the correct accessibilityIdentifier, however, none of the UITabBarButton objects has the accessibilityIdentifier of the associated tab bar item.
Does anyone know why the accessibility identifier isn't, for lack of a better word, "carrying through" to the UITabBarButton object that iOS uses?
I ran into the same problem trying to add accessibility identifiers to UITabbar items for UITesting, I was able to do it using this workaround which is not perfectly sane but it works:
[self.tabBar setItems:tabBarItems animated:animated];
NSArray *identifiers = #[#"itemIdentifier1", #"itemIdentifier2", #"itemIdentifier3"];
int index = 0;
for (UIControl *control in controller.tabBar.subviews)
{
if ([control isKindOfClass:UIControl.class] && index < identifiers.count)
{
//This is actually the UITabBarButton
control.accessibilityIdentifier = identifiers[index];
index++;
}
}
The key is to assign the identifiers to the UITabbar subviews after the tabs have been added, which means the UITabBarButton objects have been created and ready to have an identifier set.
Related
I have an iOS app with a for loop that creates, sets-up and adds a custom view controller to my view. The problem is that I need to dynamically set the UIViewController object to the correct class, depending on the current loop number. Here is my code:
// Loop through the data and setup the switches.
for (NSUInteger loop = 0; loop < [switchLabels count]; loop++) {
// Create the view controller object.
UIViewController *screen;
// Create the custom switch view.
if (loop < 3) {
screen = [[CustomSwitchView alloc] initWithNibName:#"CustomSwitchView" bundle:nil];
} else {
screen = [[CustomTripleSwitchView alloc] initWithNibName:#"CustomTripleSwitchView" bundle:nil];
}
// Create the custom switch view.
[screen setPassedInType:switchTypes[loop]];
[screen setDelegate:self];
[self addChildViewController:screen];
[screen.view setFrame:CGRectMake((self.view.frame.size.width - 150), ((UILabel *)switchLabels[loop]).frame.origin.y, 144, 72)];
[scrollTopView addSubview:screen.view];
[screen didMoveToParentViewController:self];
[screen setTitles:switchTitles[loop] state:[switchSettings[loop] boolValue]];
}
The problem is that some of the above method calls come up with the error:
No visible #interface for 'UIViewController' declares the selector....
In order to solve this problem, I need to type cast the object screen. However I need to dynamically type cast it, based on the for loop number:
If the loop is less than 3, I need to type cast the object to CustomSwitchView, otherwise I need to type cast it to CustomTripleSwitchView. How can I do this inline? For example I tried the below code, but it didn't work:
(loop < 3 ? (CustomSwitchView *) : (CustomTripleSwitchView *))
There are a few ways to handle this. Least impact to the existing code would be to distinguish methods as those that apply generically to UIViewControllers and those that are particular to the subclasses. Invoke the subclass methods on stack variables that are declared as the specific subclass...
for (NSUInteger loop = 0; loop < [switchLabels count]; loop++) {
// Create the view controller object.
UIViewController *vc;
// Create the custom switch view.
if (loop < 3) {
CustomSwitchView *screen = [[CustomSwitchView alloc] initWithNibName:#"CustomSwitchView" bundle:nil];
[screen setPassedInType:switchTypes[loop]];
[screen setDelegate:self];
[screen setTitles:switchTitles[loop] state:[switchSettings[loop] boolValue]];
vc = screen;
} else {
CustomTripleSwitchView *screen = [[CustomTripleSwitchView alloc] initWithNibName:#"CustomTripleSwitchView" bundle:nil];
[screen setPassedInType:switchTypes[loop]];
[screen setDelegate:self];
[screen setTitles:switchTitles[loop] state:[switchSettings[loop] boolValue]];
vc = screen;
}
// Create the custom switch view.
[self addChildViewController:vc];
[vc.view setFrame:CGRectMake((self.view.frame.size.width - 150), ((UILabel *)switchLabels[loop]).frame.origin.y, 144, 72)];
[scrollTopView addSubview:vc.view];
[vc didMoveToParentViewController:self];
}
This is an okay solution if we're done encountering this problem in the project. When you see this sort of thing proliferate, it's time to start thinking: (a) should I define a protocol on each class (as a commenter aptly suggested), or (b) are these really subclasses related to one another, like CustomTripleSwitchView is really a subclass of CustomSwitchView?
Basically what I'm trying to achieve is to have my scope bar to never disappear.
Environment : IOS 7, storyboard, inside a view controller I have a "search bar and search display controller" and a separate tableview (the searchbar is not inside the table)
Inside the view controller.h
#property (nonatomic, strong) IBOutlet UISearchBar *candySearchBar;
Inside the view controller.m
#synthesize candySearchBar;
What I tried : inside a custom search bar class
- (void) setShowsScopeBar:(BOOL) showsScopeBar
{
if ([self showsScopeBar] != showsScopeBar) {
[super invalidateIntrinsicContentSize];
}
[super setShowsScopeBar:showsScopeBar];
[super setShowsScopeBar: YES]; // always show!
NSLog(#"setShowsScopeBar searchbar");
NSLog(#"%hhd", showsScopeBar);
}
and
searchBarDidEndEditing
Same thing in the view controller, but then
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[candySearchBar setShowsScopeBar:YES];
[candySearchBar sizeToFit];
}
I hope my question is clear, I tried many solutions posted all over the internet, most of them talk about the setshowsscopebar, but it doesn't seem to work. The output of the log in setshowscopebar is 1, but the scopebar is still not shown.
I still consider myself to be new to the code, the fault can still be a newbie mistake.
edit : another piece of code in the view controller, as you can see i'm searching blind:
-(void)searchDisplayControllerDidEndSearch:(UISearchDisplayController *)controller{
self.searchDisplayController.searchBar.showsCancelButton = YES;
self.searchDisplayController.searchBar.showsScopeBar = YES;
controller.searchBar.showsScopeBar = TRUE;
controller.searchBar.frame = CGRectMake(0, 149, 768, 88);
UIButton *cancelButton;
UIView *topView = self.searchDisplayController.searchBar.subviews[0];
for (UIView *subView in topView.subviews) {
if ([subView isKindOfClass:NSClassFromString(#"UINavigationButton")]) {
cancelButton = (UIButton*)subView;
}
}
if (cancelButton) {
//Set the new title of the cancel button
[cancelButton setTitle:#"Cancel" forState:UIControlStateNormal];
[cancelButton setEnabled:YES];
controller.searchBar.showsScopeBar = YES;
//candySearchBar.scopeButtonTitles = [NSArray arrayWithObjects:#"Flags", #"Listeners", #"Stations", nil];
}
NSLog(#"%#",NSStringFromCGRect(controller.searchBar.frame));
NSLog(#"%#",NSStringFromCGRect(controller.searchBar.bounds));
NSLog(#"%hhd#",controller.searchBar.hidden);
}
The code you tried will not work in iOS7 onward because apple has changed it behavior of UISearchBar to hide the scope when return to normal view. Add this method to your custom searchBar class.
-(void)layoutSubviews
{
[super layoutSubviews];
if([[UIDevice currentDevice].systemVersion floatValue]>=7.0) {
//Get search bar with scope bar to reappear after search keyboard is dismissed
[[[[self.subviews objectAtIndex:0] subviews] objectAtIndex:0] setHidden:NO];
[self setShowsScopeBar:YES];
}
}
Directly accessing object at index may crash the app in iOS6 because of difference in view hierarchy between iOS6 and iOS7, to avoid this, add this inside if condition only when its iOS7.
In addition this is also required in the custom search bar class
-(void) setShowsScopeBar:(BOOL)showsScopeBar {
[super setShowsScopeBar:YES]; //Initially make search bar appear with scope bar
}
I have the same issue. Perhaps it is something that has changed in iOS7 since showing the scope bar is supposed to be the default behaviour. You can verify this in the section "Creating an Optional Scope Bar to Filter Results" of the following tutorial:
http://www.raywenderlich.com/16873/how-to-add-search-into-a-table-view
Hopefully someone has a solution for this; otherwise we will have to look for a workaround.
initialize set scope bar NO
[self.searchBar setShowsScopeBar:NO];
[self.searchBar sizeToFit];
//default scope bar selection
self.searchBar.selectedScopeButtonIndex=3;
unselect/remove tick from scopeBar checkbox
It's possible (but hacky) to do this without a custom searchBar, in a pretty similar way to what CoolMonster suggests.
In your TableViewController, this will show the ScopeBar after a search ends:
- (void)searchDisplayControllerDidEndSearch:(UISearchDisplayController *)controller
{
//Show the scopeBars
controller.searchBar.showsScopeBar = YES;
//Resize the searchBar to show ScopeBar
controller.searchBar.frame = CGRectMake(0, 0, 320, 88);
if([[UIDevice currentDevice].systemVersion floatValue]>=7.0) {
[[[[controller.searchBar.subviews objectAtIndex:0] subviews] objectAtIndex:0] setHidden:NO];
}
}
Then, since you probably want it to appear before you search, add this line to the TableViewController's viewDidLoad:
[self searchDisplayControllerDidEndSearch:self.searchDisplayController];
For the record, after getting this to work, I ended up using a separate segmented control instead of the approach above for several reasons, not least of which was that touching the ScopeBar of a SearchBar, once you get it to display, launches the search display tableView, which makes of sense if you're using it the recommended way. However, since I wanted the ScopeBar to work without launching the search tableview, for me it made more sense just to use my own segmented control and add it to my tableHeaderView under the searchBar.
I have a view controller, named AllThingsViewController that dynamically creates other view controllers, named ThingViewController, and adds their top level view to a UIScrollView. (I'm writing proprietary code so I've changed the names of my classes, but the structure of my code is exactly the same.)
Here's what its loadView method contains:
NSArray *things = [[ThingDataController shared] getThings];
if ([things count] == 0) {
// code in this block is not relevant as it's not being executed...
} else {
for(unsigned int i = 0; i < [things count]; ++i) {
ThingViewController *thingViewController = [[ThingViewController alloc] init];
[thingViewController loadView];
[scrollView addSubview:thingViewController.topView];
thingViewController.topView.frame = CGRectNewOrigin(thingViewController.topView.frame,
0, thingViewController.topView.frame.size.height*i);
[thingViewController displayThing:thing[i]];
}
}
ThingViewController's loadView method looks like this:
- (void)loadView
{
NSArray *topLevelObjs = nil;
topLevelObjs = [[NSBundle mainBundle] loadNibNamed:#"ThingView" owner:self options:nil];
if (topLevelObjs == nil)
{
NSLog(#"Error: Could not load ThingView xib\n");
return;
}
}
When my app starts up everything displays correctly, until I try to tap one of the buttons that exists in the xib being loaded by ThingViewController, at which point it crashes due to an exception: "unrecognized selector sent to instance". It seems that ARC is releasing my ThingViewController instances too early.
Looking at my code, I figured it was because they weren't being held on to anything, so I created an NSMutableArray as an instance variable in my AllThingsViewController class, and started adding the ThingViewControllers to it thusly:
NSArray *things = [[ThingDataController shared] getThings];
if ([things count] == 0) {
// not being executed...
} else {
for(unsigned int i = 0; i < [things count]; ++i) {
ThingViewController *thingViewController = [[ThingViewController alloc] init];
[thingViewController loadView];
[scrollView addSubview:thingViewController.topView];
thingViewController.topView.frame = CGRectNewOrigin(thingViewController.topView.frame,
0, thingViewController.topView.frame.size.height*i);
[thingViewController displayThing:thing[i]];
[allThingsViewControllers addObject:thingViewController];
}
}
However, it didn't change anything, even though those objects are being added to the array. Finally, just to confirm that this is ARC releasing it early, I changed "thingViewController" to be an instance variable in AllThingsViewController and changed:
ThingViewController *thingViewController = [[ThingViewController alloc] init];
to be:
thingViewController = [[ThingViewController alloc] init];
Sure enough, the last item in the scrollable list doesn't crash when I tap its buttons, but the other ones do, because its ThingViewController isn't being deallocated.
I'm still relatively new to ARC, but after a bunch of Googling I have no idea how to fix this. What do I do?
Couple of things.
Problem 1:
This looks like the cause of your bug:
[allBillViewControllers addObject:billViewController];
It should be:
[allBillViewControllers addObject:thingViewController];
Right?
Problem 2
You are not properly adding the view controller to your view hierarchy. It should be this:
[self addChildViewController:childViewController];
[childViewController.view setFrame:targetFrame];
[scrollView addSubview:childViewController.view];
[childViewController didMoveToParentViewController:self];
And similarly when removing a child view controller:
[childViewController willMoveToParentViewController:nil];
[childViewController.view removeFromSuperview];
[childViewController removeFromParentViewController];
Problem 3
Never call loadView explicitly on a view controller. It gets called by UIKit whenever you access the view property of a view controller.
Problem 4
You must add the view of the child view controller to your scroll view, not an arbitrary subview topView in its view hierarchy. Refactor your ThingViewController class to make this simpler for yourself. :-)
Let's look at your code:
for(unsigned int i = 0; i < [things count]; ++i) {
ThingViewController *thingViewController = [[ThingViewController alloc] init];
[thingViewController loadView];
[scrollView addSubview:thingViewController.topView];
thingViewController.topView.frame = CGRectNewOrigin(thingViewController.topView.frame,
0, thingViewController.topView.frame.size.height*i);
[billViewController displayThing:thing[i]];
[allBillViewControllers addObject:billViewController];
}
After each loop of the for loop executes, nothing will have a strong reference to the ThingViewController. Thus, it gets released and destroyed.
If ThingViewController is a subclass of UIViewController, then it should be made a "child view controller" of the scrollview's view controller. I recommend reading the section on from the View Controller Programming Guide on creating custom container view controllers (i.e., a view controller that encapsulates and displays other view controllers).
i've got a question regarding a UIScrollView with paging enabled that contains many UIView, each managed by an own UIViewController.
Right now there are about 20 to 30 UIViewControllers that COULD be contained in the UIScrollView. It's a catalog app on the iPad, and I started preloading all the views at the beginning, but with the amount of UIViewControllers getting bigger and bigger, that is not an option any more.
I'm looking for the perfect solution in terms of memory usage. It's no problem to reload the UIViewControllers when the ContentOffset of the ScrollView reaches a specific controller. And I think to nil the UIViewControllers when the ContentOffset tells me that the UIViewControllers is not needed any more isn't that hard as well.
What is the correct way to handle this? Is it enough to alloc the UIViewControllers when needed, putting them into a NSMutableDictionary or NSMutableArray and nil them when they are not needed any more? A little bit of help from someone already having done something similar would be great!
Thanks for your help!
I'm sure there are some good infinite scrolling classes out there, but if you were going to "roll your own", here is a minimalist bit of code that demonstrates the process of infinite scrolling, keeping the current, previous, and next pages in memory, but letting go of anything else. This assumes that:
you're doing horizontal scrolling and have turned on paging;
that you're using view controllers for the child views;
your child view controller class has a page property to keep track of what page it's for; and
you've made your view controller the delegate for your scroll view
Thus, it might look like:
- (void)viewDidLoad
{
[super viewDidLoad];
// my underlying model is just an array of strings, which I'll show on my child
// view; your model will be more elaborate, but I just want to illustrate the concept
self.objects = #[#"1", #"2", #"3", #"4", #"5", #"6", #"7", #"8", #"9"];
// set the `contentSize` for the scrollview
CGRect content = self.view.bounds;
content.size.width *= [self.objects count]; // make it wide enough to hold everything
self.scrollView.contentSize = content.size;
// set our current page and load the first pages (the first and the next pages)
self.currentPage = 0;
[self addChildPage:0 toScrollView:self.scrollView];
[self addChildPage:1 toScrollView:self.scrollView];
}
- (void)addChildPage:(NSInteger)page toScrollView:(UIScrollView *)scrollView
{
// create the child controller
ChildViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"child"];
// set whatever properties you need to in order for it to present its information correctly
controller.text = self.objects[page];
controller.page = page;
// now do the stuff to add it to the right place in the scrollview
CGRect frame = self.view.bounds;
frame.origin.x = frame.size.width * page;
controller.view.frame = frame;
[self addChildViewController:controller]; // containment call for adding child view controller
[scrollView addSubview:controller.view];
[controller didMoveToParentViewController:self]; // containment call when done adding child
}
- (ChildViewController *)childControllerForPage:(NSInteger)page
{
for (ChildViewController *controller in self.childViewControllers)
{
if (controller.page == page)
return controller;
}
return nil;
}
- (void)addChildIfNecessary:(NSInteger)page toScrollView:(UIScrollView *)scrollView
{
if (page < 0 || page >= [self.objects count])
return;
ChildViewController *controller = [self childControllerForPage:page];
if (controller == nil)
[self addChildPage:page toScrollView:scrollView];
}
- (void)removeChildController:(UIViewController *)controller
{
[controller willMoveToParentViewController:nil]; // containment call before removing child
[controller.view removeFromSuperview];
[controller removeFromParentViewController]; // containment call to remove child
}
- (void)updateChildrenViewsForPage:(NSInteger)page forScrollView:(UIScrollView *)scrollView
{
if (page == self.currentPage)
return;
// add child pages as necessary
[self addChildIfNecessary:page toScrollView:scrollView];
[self addChildIfNecessary:(page-1) toScrollView:scrollView];
[self addChildIfNecessary:(page+1) toScrollView:scrollView];
// find any pages that need removing
NSMutableArray *pagesToRemove = [NSMutableArray array];
for (ChildViewController *controller in self.childViewControllers)
{
if (controller.page < (page - 1) ||
controller.page > (page + 1))
{
[pagesToRemove addObject:controller];
}
}
// remove them if they need removing
for (UIViewController *controller in pagesToRemove)
{
[self removeChildController:controller];
}
// update our "current page" index
self.currentPage = page;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
NSInteger page = scrollView.contentOffset.x / scrollView.frame.size.width + 0.5;
[self updateChildrenViewsForPage:page forScrollView:scrollView];
}
This demonstrates the appropriate custom container calls and the handling of the scroll events. I hope this helps.
I have an app the loaded many view controllers in a scroll view depending on the number of objects the user has in a tableview. So when I flip between the tableview and the scroll view, the number of view controllers in the scroll view changes according to how many objects the user has in the tableview.
I use the code in Apple's PageControl sample code to build the scroll view with many view controllers inside it, after some modification of course.
- (void)loadScrollViewWithPage:(int)page
{
if (page < 0) return;
if (page >= kNumberOfPages) return;
// replace the placeholder if necessary
MainViewController *countdownController = [viewControllers objectAtIndex:page];
if ((NSNull *)countdownController == [NSNull null])
{
id occasion = [eventsArray objectAtIndex:page];
countdownController = [[MainViewController alloc] initWithPageNumber:page];
[countdownController setOccasion:occasion];
[viewControllers replaceObjectAtIndex:page withObject:countdownController];
[countdownController release];
}
// add the controller's view to the scroll view
if (nil == countdownController.view.superview)
{
CGRect frame = scrollView.frame;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0;
countdownController.view.frame = frame;
[scrollView addSubview:countdownController.view];
}
}
The problem is the number of living view controllers (MainViewController here) keeps increasing when I flip between the table view and the scroll view (according to Instruments) even though I didn't add any new objects which causes memory problems of course.
I tried so many things in viewWillDisappear of the scroll view like:
- (void) viewWillDisappear:(BOOL)animated
{
//test unloading all views
//Remove all subviews
[[scrollView subviews] makeObjectsPerformSelector:#selector(removeFromSuperview)];
//[[scrollView subviews] makeObjectsPerformSelector:#selector(release)];
//[viewControllers removeAllObjects];
for (unsigned m = 0; m < [viewControllers count]; m++)
{
//[[viewControllers objectAtIndex:m] makeObjectsPerformSelector:#selector(release)];
[viewControllers removeObjectAtIndex:m];
}
}
But it didn't work.
Here is a recording of how the app works youtube.com/watch?v=5W8v_smZSog
And this is the viewWillAppear method of the scroll view:
- (void)viewWillAppear:(BOOL)animated
{
eventsArray = [[NSMutableArray alloc] init];
kNumberOfPages = [self.dataModel occasionCount];
//update the eventsArray from the dataModel
//Fill in the events Array with occasions form the data model
for (unsigned r = 0; r < kNumberOfPages; r++)
{
Occasion* occasion = [self.dataModel occasionAtIndex:r];
[eventsArray insertObject:occasion atIndex:r];
}
// view controllers are created lazily
// in the meantime, load the array with placeholders which will be replaced on demand
NSMutableArray *controllers = [[NSMutableArray alloc] init];
for (unsigned i = 0; i < kNumberOfPages; i++)
{
[controllers addObject:[NSNull null]];
}
self.viewControllers = controllers;
[controllers release];
// a page is the width of the scroll view
scrollView.pagingEnabled = YES;
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * kNumberOfPages, scrollView.frame.size.height);
scrollView.showsHorizontalScrollIndicator = NO;
scrollView.showsVerticalScrollIndicator = NO;
scrollView.scrollsToTop = NO;
scrollView.delegate = self;
pageControl.numberOfPages = kNumberOfPages;
pageControl.currentPage = currentPage;
[self loadScrollViewWithPage:0];
[self loadScrollViewWithPage:1];
}
UPDATE: Video recording of Instruments http://www.youtube.com/watch?v=u1Rd2clvMQE&feature=youtube_gdata_player
And a screen shot showing the responsible caller:
Thank you.
This is for you if you don't want to use UIPageViewController (read my other answer).
The sample project is designed for a constant number of pages (kNumberOfPages). The scrollview content size and the size of the view controller array depends on the number of pages. The sample code set this up in awakeFromNib, which is called only once.
So in order to make this dynamic you could recreate the whole ContentController when the number of pages changes. You just need to add a property for the number of pages.
The other option would be to reset the scrollview and view controller array when the number of pages changes.
I'm assuming you have defined a property for the events:
#property(nonatomic,retain) NSArray* eventsArray;
You could then add a setter method like this:
-(void)setEventsArray:(NSArray *)eventsArray
{
if (eventsArray != _eventsArray) {
[_eventsArray release];
_eventsArray = [eventsArray retain];
NSUInteger eventCount = [eventsArray count];
//reset scrollview contentSize
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * eventCount, scrollView.frame.size.height);
// reset content offset to zero
scrollView.contentOffset = CGPointZero;
//remove all subviews
[[scrollView subviews] makeObjectsPerformSelector:#selector(removeFromSuperview)];
pageControl.numberOfPages = eventCount;
// reset viewcontroller array
NSMutableArray *controllers = [[NSMutableArray alloc] init];
for (unsigned i = 0; i < eventCount; i++)
{
[controllers addObject:[NSNull null]];
}
self.viewControllers = controllers;
[controllers release];
[self loadScrollViewWithPage:0];
[self loadScrollViewWithPage:1];
}
}
You call this method from the table view controller at the time when the user switches to the scroll view.
Apple's PageControl sample code is 2 years old and you can consider it as deprecated because there is a new container view controller in iOS 5 that does all this: UIPageViewController.
You should really start using UIPageViewController, then you don't need that loadScrollViewWithPage method at all. It would be less code and more easy.
Take a look at the PhotoScroller sample code. It has been updated to take full advantage of UIPageViewController.
It doesn't look like you are implementing Apple's View Controller Containment pratices. It would make memory management that much easier and safer.
Plus, hoping that it might save you a lot of future headaches, there is already an open source project that does what you are describing (implementing a self-managing scrollview of an arbritary number of view controllers).
You might want to take a look at it: RHHorizontalSwipe.
The concept of a UIScrollView containing multiple UIViewController views sounds sketchy at best, that design does not sound good at all.
That being said, one potential issue could be this line:
if ((NSNull *)countdownController == [NSNull null])
You would be better off with something like this:
if (!countdownController || [countdownController isKindOfClass:[NSNull class]])
Also, you should call [super viewWillDisappear:animated] in your viewWillDisappear method.