Programmatically create a UINavigationController with a UICollectionViewController embed in a container - ios

What I'd like to do is create a navigation controller based on a layout. Therefor I created a method renderLayout in my UIStoryboard class.
renderLayout:
UINavigationController *navigationController = [[UINavigationController alloc] init];
NSMutableArray *viewControllers = [[NSMutableArray alloc] init];
/* navigation bar styling */
// navigationController.toolbar.translucent = false;
// navigationController.title = #"TEST";
UIViewController *controller = [[UIViewController alloc] init];
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 1024, 768)];
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 1024, 768)];
scrollView.contentSize = CGSizeMake(1024, /* some calculated value here */);
/* controller styling */
//controller.title = [page valueForKey:#"title"];
NSArray *items = /* ... */
int containerHeight = (1 + ([items count] / 3)) * 29;
MultiSelectionViewController *multiSelection = [[MultiSelectionViewController alloc] initWithItems:[data valueForKey:#"items"]];
multiSelection.view.frame = CGRectMake(20, 65, 984, containerHeight);
/* for debugging */
//multiSelection.view.backgroundColor = [UIColor redColor];
[scrollView addSubview:multiSelection.view];
[controller addChildViewController:multiSelection];
[multiSelection didMoveToParentViewController:controller];
[view addSubview:scrollView];
[controller.view addSubview:view];
[viewControllers addObject:controller];
[navigationController pushViewController:controller animated:true];
return navigationController;
The MultiSelection is a UICollectionViewController<UICollectionViewDataSource> kind of class that creates a collection of items which can be selected.
MultiSelectionViewController init method:
- (id)initWithItems:(NSArray*)items
{
self = [super init];
if (self) {
self.items = items;
UICollectionViewLayout *layout = [[UICollectionViewLayout alloc] init];
self.view = [[UICollectionView alloc] initWithFrame:CGRectMake(20, 0, 984, 29) collectionViewLayout:layout];
self.collectionView.dataSource = self;
}
return self;
}
After a uses pushes a button on that current UIViewController the method gets called. It should then construct the navigation controller which I return and later load with
[self presentViewController:renderedNavController animated:true completion:^(){}];
So far everything is working quite well. The one problem I have is that It displays all kinds of labels and buttons and stuff that I can directly push onto the scrollView but it fails to load the MultiSelection elements. It displays a red rectangle in the size of the view frame but viewDidLoad never gets called inside the MultiSelectionViewController. Can someone help me with this? I've already done a lot of google research but couldn't find any solution yet. I hope I explained my problem thoroughly, if not, ask away :)

Okay I finally found the answer. In order to have the viewDidLoad method called I have to provide a layout and init the view controller with it.
Hence self = [super init]; is wrong, the correct way to do it is self = [super initWithCollectionViewLayout:layout
On a side note, I had to use UICollectionViewFlowLayout (instead of UICollectionViewLayout) and use the method setItemSize to provide the correct size of the cells.

Related

UINavigationBar with UISegmentedControl partially covers childViews

I have read many other threads on this and the Apple docs, but haven't found a solution yet for my particular problem.
My app uses a UITabBarController as the rootViewController, and in one of the tabs I have a UISegmentedControl in the navigationBar to switch between three child UITableViewControllers.
(In the real app two of the childVCs are a custom UIViewController, I'm just using three UITableViewControllers for the sample app).
The segmentedControl setup and the switching all works fine. The thing that goes wrong is that only the first UITableViewController is shown correctly. For the second and third one, part of the first cell is hidden under the navigationBar. When I click through all three, the first one is still ok.
I have made a little sample app to show what's going on, using very bright colors for demonstration purposes: https://www.dropbox.com/s/7pfutvn5jba6rva/SegmentedControlVC.zip?dl=0
Here is also some code (I'm not using storyboards):
// AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
FirstViewController *fvc = [[FirstViewController alloc] init];
UINavigationController *firstNavigationController = [[UINavigationController alloc] initWithRootViewController: fvc];
SecondViewController *svc = [[SecondViewController alloc] init];
UINavigationController *secondNavigationController = [[UINavigationController alloc] initWithRootViewController: svc];
// Initialize tab bar controller, add tabs controllers
UITabBarController *tabBarController = [[UITabBarController alloc] init];
tabBarController.viewControllers = #[firstNavigationController, secondNavigationController];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.rootViewController = tabBarController;
[self.window makeKeyAndVisible];
return YES;
}
// FirstViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.title = #"One";
self.view.backgroundColor = [UIColor orangeColor];
UITableViewController *vc1 = [[UITableViewController alloc] init];
UITableViewController *vc2 = [[UITableViewController alloc] init];
UITableViewController *vc3 = [[UITableViewController alloc] init];
vc1.view.backgroundColor = [UIColor redColor];
vc2.view.backgroundColor = [UIColor blueColor];
vc3.view.backgroundColor = [UIColor greenColor];
self.viewControllers = #[vc1, vc2, vc3];
self.segmentTitles = #[#"Red", #"Blue", #"Green"];
self.segmentedControl = [[UISegmentedControl alloc] initWithItems: self.segmentTitles];
[self.segmentedControl addTarget: self
action: #selector(segmentClicked:)
forControlEvents: UIControlEventValueChanged];
self.navigationItem.titleView = self.segmentedControl;
self.segmentedControl.selectedSegmentIndex = 0;
// set the first child vc:
UIViewController *vc = self.viewControllers[0];
[self addChildViewController: vc];
vc.view.frame = self.view.bounds;
[self.view addSubview: vc.view];
self.currentVC = vc;
}
- (void)segmentClicked:(id)sender
{
if (sender == self.segmentedControl)
{
NSUInteger index = self.segmentedControl.selectedSegmentIndex;
[self loadViewController: self.viewControllers[index]];
}
}
- (void)loadViewController:(UIViewController *)vc
{
[self addChildViewController: vc];
[self transitionFromViewController: self.currentVC
toViewController: vc
duration: 1.0
options: UIViewAnimationOptionTransitionFlipFromBottom
animations: ^{
[self.currentVC.view removeFromSuperview];
vc.view.frame = self.view.bounds;
[self.view addSubview: vc.view];
} completion: ^(BOOL finished) {
[vc didMoveToParentViewController: self];
[self.currentVC removeFromParentViewController];
self.currentVC = vc;
}
];
}
So obviously my question is, why does this happen, and what can I do to fix it?
Edit: adding screenshots.
EDIT: Based on the answer below I changed the code in the animation block to:
[self.currentVC.view removeFromSuperview];
if ([vc.view isKindOfClass: [UIScrollView class]])
{
UIEdgeInsets edgeInsets = UIEdgeInsetsMake(self.topLayoutGuide.length, 0, self.bottomLayoutGuide.length, 0);
[UIView performWithoutAnimation: ^{
vc.view.frame = self.view.bounds;
((UIScrollView *)vc.view).contentInset = edgeInsets;
((UIScrollView *)vc.view).scrollIndicatorInsets = edgeInsets;
}];
}
else
{
vc.view.frame = self.view.bounds;
}
[self.view addSubview: vc.view];
Now it works. I'm going to try this with a custom UIViewController as well.
The issue is that you do not set the correct content inset to each table view. The system attempts to do it for you, but I guess your setup is too complex for it, and it only does it for the first tableview that is loaded in viewDidLoad. In your loadViewController: method, when replacing the currently displayed view, make sure to set both the contentInset and scrollIndicatorInsets to the values of the previous view. I think the system will manage to set the correct insets later, in case you rotate to landscape. Try it. If it doesn't, you will need to do it on your own in viewDidLayoutSubviews.

Can't change size for UIPopover?

I'm writing an app that needs to spawn a popover in order to add a new note. To that end, I have something that does kind of the trick, however, I can't seem to adjust the size of the popover. This is how I'm spawning it:
UIButton* btn =sender;
UIViewController* fooTroller = [[UIViewController alloc] init];
CGRect rectFoo = CGRectMake(0, 0, 100, 100);
UIView* fooView = [[UIView alloc] initWithFrame:rectFoo];
[fooView setBackgroundColor:[UIColor redColor]];
[fooTroller setView:fooView];
popOver =[[UIPopoverController alloc] initWithContentViewController:fooTroller];
[popOver presentPopoverFromRect:btn.frame
inView:self.view
permittedArrowDirections:UIPopoverArrowDirectionLeft
animated:YES];
Any thoughts? It's not respecting the view size.
You should not be calling setView: on the view controller. Let the view controller setup its own view.
The proper way to size a popover is to either override the contentSizeForViewInPopover method of your view controller to return the size or to set the popoverContentSize property on the popover.
UIButton* btn =sender;
UIViewController* fooTroller = [[UIViewController alloc] init];
popOver = [[UIPopoverController alloc] initWithContentViewController:fooTroller];
popOver.popoverContentSize = CGSizeMake(100, 100);
[popOver presentPopoverFromRect:btn.frame
inView:self.view
permittedArrowDirections:UIPopoverArrowDirectionLeft
animated:YES];

How to display a popover programmatically from a uibutton that is also created programmatically (Not using interface builder)

I have a button I have created programmatically within a view controller. Once the button is pressed I want it to to use a method to create the popover programmatically.
The button which is created in the ViewDidLoad in my view controller.m
UIView *moreFundInfoView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 540, 620)];
[self.view addSubview:moreFundInfoView];
[moreFundInfoView setBackgroundColor:[UIColor RMBColor:#"b"]];
btnContact = [UIButton buttonWithType:(UIButtonTypeRoundedRect)];
[btnContact setFrame:CGRectMake(390, 575, contactButton.width, contactButton.height)];
btnContact.hidden = NO;
[btnContact setTitle:#"Contact" forState:(UIControlStateNormal)];
[moreFundInfoView addSubview:btnContact];
[btnContact addTarget:self action:#selector(showContactDetails:) forControlEvents:UIControlEventTouchUpInside];
Then I have the method I use when the button is pressed.
-(void) showContactDetails: (id) sender
{
UIViewController *popoverContent = [[UIViewController alloc]init];
UIView *popoverView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 200, 300)];
[popoverView setBackgroundColor:[UIColor RMBColor:#"b"]];
popoverContent.view = popoverView;
popoverContent.contentSizeForViewInPopover = CGSizeMake(200, 300);
UIPopoverController *contactPopover =[[UIPopoverController alloc] initWithContentViewController:popoverContent];
[contactPopover presentPopoverFromRect:btnContact.frame inView:self.view permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES ];
[contactPopover setDelegate:self];
}
What am I missing here? Cause it runs fine, but as soon as I click the button the app crashes. I think it is a delegate issue, but I am not sure. Any advice would be appreciated.
I think this code will help you. You are certainly missing delegate methods
ViewController *viewController = [[ViewController alloc] initWithNibName:#"ViewController" bundle:nil];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewController];
UIPopoverController *popover = [[UIPopoverController alloc] initWithContentViewController:navigationController];
popover.delegate = self;
popover.popoverContentSize = CGSizeMake(644, 425); //your custom size.
[popover presentPopoverFromRect:button.frame inView:self.view permittedArrowDirections: UIPopoverArrowDirectionLeft | UIPopoverArrowDirectionUp animated:YES];
Just make sure you are not forgetting UIPopover Delegate methods or else application will definitely crash. It is mandatory.
UIViewController *controller = [[UIViewController alloc] init];
[view removeFromSuperview]; //view is a view which is displayed in a popover
controller.view = view;
UIPopoverController *popover = [[UIPopoverController alloc] initWithContentViewController:controller];
popover.delegate = self;
[popover presentPopoverFromRect:button.frame inView:self.view permittedArrowDirections:UIPopoverArrowDirectionLeft animated:YES];
All I had to do was change the property from "retain" to "strong" in the .h file and it works, stopped the app from crashing.
Yes, changing property for "retain" to "strong" makes you to hold your picker view object.
I think the problem with your code was, UIPopoverController object gets deallocated automatically when method finishes.
making strong property gets you to strongly point an object.

Greedy UITabBarController?

I'm making a custom UIViewController which holds to sub view controllers. They are put inside the main view controller this way:
- (void) setHeaderViewController:(UIViewController *)vc
{
[self setViewController:vc isHeader:YES];
_headerViewController = vc;
}
- (void) setDetailViewController:(UIViewController *)vc
{
[self setViewController:vc isHeader:NO];
_detailViewController = vc;
}
- (void) setViewController:(UIViewController*) viewController isHeader:(BOOL)isHeader
{
UIViewController* viewControllerRef = ((isHeader) ? _headerViewController : _detailViewController);
if(viewControllerRef == viewController)
return;
const CGSize selfSize = self.view.frame.size;
CGRect viewFrame;
if (isHeader)
{
viewFrame = CGRectMake(0, 0, selfSize.width, 150);
}
else
{
viewFrame = CGRectMake(0, 150, selfSize.width, selfSize.height-150);
}
UIView* viewControllerView = viewController.view;
[viewControllerView setFrame:viewFrame];
[viewControllerView setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | ((isHeader) ? UIViewAutoresizingFlexibleBottomMargin : UIViewAutoresizingFlexibleTopMargin))];
[self.view addSubview:viewControllerView];
}
If I put two UIViewControllers inside of each sub view controller slot, the sizes defined in the method is respected. However, when putting a UITabBarController, it gets greedy and occupies the whole space:
UIViewController *vc = [[WIFCustomerDetailHeaderViewController alloc] initWithNibName:#"WIFCustomerDetailHeaderViewController" bundle:nil];
[self setHeaderViewController:vc];
UIViewController *vc1 = [[UIViewController alloc] init];
vc1.title = #"Personal Data";
vc1.tabBarItem = [[UITabBarItem alloc] initWithTitle:#"Personal Data" image:nil tag:0];
vc1.view.backgroundColor = [UIColor redColor];
vc1.view.frame = CGRectMake(0,400,100,100);
UIViewController *vc2 = [[UIViewController alloc] init];
vc2.title = #"Diagnosis";
vc2.tabBarItem = [[UITabBarItem alloc] initWithTitle:#"Diagnosis" image:nil tag:0];
vc2.view.backgroundColor = [UIColor purpleColor];
vc2.view.frame = CGRectMake(0,500,50,50);
NSArray *tabs = [[NSArray alloc] initWithObjects:vc1, vc2, nil];
WIFCustomerDetailTabViewController *tvc = [[WIFCustomerDetailTabViewController alloc] init];
[tvc setViewControllers:tabs];
[self setDetailViewController:tvc];
Does anyone knows what can be happening here? I can't find the cause...
Images below..
Documentation for the UITabBarController class says, "you must install this view as the root of your window. Unlike other view controllers, a tab bar interface should never be installed as a child of another view controller".
It seems that you're attempting a design that's specifically not supported.

UIPopoverController - SIGABRT when Presented

I'm attempting to present a popoverView when a barButton is pressed. Unfortunately, the app crashes with the signal SIGABRT every time I try to call it. The "Empty.xib" does have a view designed in it. (As I'm using a barButton, I was unable to use the frame, if you have a workaround for that, please say so too).
- (IBAction)loadPopover:(id)sender
{
UIView *someView = [[UIView alloc] init];
UIViewController *someVC = [[UIViewController alloc] initWithNibName:#"Empty.xib" bundle:[NSBundle mainBundle]];
UIPopoverController *popoverController = [[UIPopoverController alloc] initWithContentViewController:someVC];
someView.frame = CGRectMake(0, 44, 320, 372);
someView.backgroundColor = [UIColor lightGrayColor];
popoverController.delegate = self;
self.popoverView = popoverController;
[self.popoverView presentPopoverFromBarButtonItem:sender permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
// CGRect popoverRect = [self.view convertRect:[btn frame] fromView:[btn superview]];
CGRect popoverRect = CGRectMake(0, 88, 320, 311);
popoverRect.size.width = MIN(popoverRect.size.width, 100);
[self.popoverView presentPopoverFromRect:popoverRect inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
Remove .xib part from initWithNibName:#"Empty.xib". Change line to this:
UIViewController *someVC = [[UIViewController alloc] initWithNibName:#"Empty" bundle:nil];
Edit:
From here -
The nib file name should not contain any leading path information.
Few suggestions here.
Check if the view's outlet is connected
Why do you need to allocate a UIView like the following? UIView *someView = [[UIView alloc] init];
Check if the sender is of type of UIBarButtonItem
What is the retain policy for self.popoverView? Check if it is retain or strong (if ARC)
Why do you present the popover two times?
Said this, here a simple snippet.
UIBarButtonItem* barButton = (UIBarButtonItem*)sender;
UIViewController *someVC = [[UIViewController alloc] initWithNibName:#"Empty" bundle:[NSBundle mainBundle]];
UIPopoverController *popoverController = [[UIPopoverController alloc] initWithContentViewController:someVC];
popoverController.delegate = self;
self.popoverView = popoverController;
[self.popoverView setPopoverContentSize:CGSizeMake(300, 200)];
[self.popoverView presentPopoverFromBarButtonItem:barButton permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
P.S. Make attention to memory if you don't use ARC.
Hope it helps.

Resources