ipad objective c using removeFromSuperview to remove UICollectionViewController throws an error - objective-c

So I'm customizing this control I found since I think it works very well except with this issue of mine:
http://www.cocoacontrols.com/controls/fsverticaltabbarcontroller
I wanted to load a UICOllectionViewCOntroller instead of a regular ViewController whenever an item is tapped on the sidebar. So I did this modification when selecting an item:
- (void)setSelectedIndex:(NSUInteger)selectedIndex
{
NSLog(#"selected Index is %#", [NSNumber numberWithInt:selectedIndex]);
NSLog(#"_selected Index is %#", [NSNumber numberWithInt:_selectedIndex]);
NSLog(#"vc counts is %i", [self.viewControllers count]);
if (selectedIndex != _selectedIndex && selectedIndex < [self.viewControllers count])
{
// add new view controller to hierarchy
UIViewController *selectedViewController = [self getSelectedVCWithSelectedIndex:selectedIndex];
[self addChildViewController:selectedViewController];
selectedViewController.view.frame = CGRectMake(self.tabBarWidth,
0,
self.view.bounds.size.width-self.tabBarWidth,
self.view.bounds.size.height);
selectedViewController.view.autoresizingMask = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth;
[self.view addSubview:selectedViewController.view];
// remove previously selected view controller (if any)
if (_selectedIndex != NSNotFound)
{
UIViewController *previousViewController = [self.viewControllers objectAtIndex:_selectedIndex];
NSLog(#"ERROR HERE: remove previous: previousVC = %#", previousViewController);
[previousViewController.view removeFromSuperview];
[previousViewController removeFromParentViewController];
}
// set new selected index
_selectedIndex = selectedIndex;
// update tab bar
if (selectedIndex < [self.tabBar.items count])
{
self.tabBar.selectedItem = [self.tabBar.items objectAtIndex:selectedIndex];
}
// inform delegate
if ([self.delegate respondsToSelector:#selector(tabBarController:didSelectViewController:)])
{
[self.delegate tabBarController:self didSelectViewController:selectedViewController];
}
}
}
So what I did is since it already handles the index number of the items on teh sidebar, I just made sure it instantiates the type of controller it needs to load using this line, I have 3 regular VC and 1 collection VC:
UIViewController *previousViewController = [self.viewControllers objectAtIndex:_selectedIndex];
This is how it looks like:
-(UIViewController *)getSelectedVCWithSelectedIndex:(NSUInteger)selectedIndex{
UIViewController *selectedVC = [[UIViewController alloc]init];
// do a switch case on this.
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
// need to instantiate each and every custom uinav
switch(selectedIndex){
case 1:
selectedVC = [storyboard instantiateViewControllerWithIdentifier:#"UINavAdminCategoryIndexViewControllerID"];
break;
case 2:
selectedVC = [storyboard instantiateViewControllerWithIdentifier:#"UINavAdminParticipantIndexViewControllerID"];
break;
case 3:
selectedVC = [storyboard instantiateViewControllerWithIdentifier:#"UINavAdminTablesIndexCollectionViewControllerID"];
break;
case 4:
selectedVC = [self.viewControllers objectAtIndex:selectedIndex];
break;
default:
break;
}
return selectedVC;
}
Now everything would load smoothly, but whenever I would go to the Collection VC tab and then move away from it by going to another tab it would throw this error:
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'UICollectionView must be
initialized with a non-nil layout parameter'
The application bombs on this part when I remove it from the superview:
[previousViewController.view removeFromSuperview];
Was wondering why it would instantiate the UIView again when all I'm doing is removing it from the stack (is that the right term?)
EDIT: Added some more codes

So I finally figured it out and hopefully someone else can find this useful. When generating a UiCollectionView you need to initiate the Layout for some reason. Idk why, I will try to find out. But this is what led me to the solution:
http://www.rqna.net/qna/ikvmhu-uicollectionview-must-be-initialized-with-a-non-nil-layout-parameter.html
Before when I instantiated the CollectionViewController on the main ViewController before the FSVerticalTabbar is called I just used the class connected to the ViewController on the storyboard eg. AdminMainController, AdminEventsCollectionController, etc.
I basically just added the Layout and used that for the UICollectionViewController when being initiated. It now removes the VC without any errors.
UICollectionViewFlowLayout *aFlowLayout = [[UICollectionViewFlowLayout alloc] init];
[aFlowLayout setItemSize:CGSizeMake(200, 140)];
[aFlowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
myCollectionViewController = [[MyCollectionViewController alloc]initWithCollectionViewLayout:flowLayout];

Related

Stacking multiple UIViewControllers and presenting the last one in UINavigationController stack

Possibly simple request here but I can't find the solution and it is bugging me for days.
I'm building simple options page where users could jump to desired page and I'm using UINavigationController instance to manage hierarchy. My storyboard looks like this:
Viewcontrollers are connected with push segues fired on next button, while I use [self.navigationController popViewControllerAnimated:YES] for previous button. If I connect, for instance, button labeled 2 on 5VC with 2VC through push segue, I get to the second page, but if I want to use previous button I will land to options page or 5VC which is something I don't want. Instead, I would like to be able to use previous button to go to first page, while on second page.
The way I see it, if I am on third page (3VC) and I call options page (5VC) and select button 3, system should stack 1VC-2VC and present 3VC, so I would be able to go to 2VC through [self.navigationController popViewControllerAnimated:YES] request.
I think the solution is somehow connected with setViewControllers:(NSArray *)viewControllers animated:(BOOL)animated, but I don't know the syntax how to make things work.
You have 3 cases
Back to one of ancestors in the middle with push
case 5VC=>2VC, 5VC=>3VC:
NSArray *vcs = self.navigationController.viewControllers;
for(NSInteger i = vcs.count - 2; i > 0; i--) {
// find the target and its parent view controller
// i.e. class of 2VC is ViewController2
if([vcs[i] isKindOfClass:[ViewController2 class]]) {
UIViewController *target = vcs[i];
UIViewController *parent = vcs[i - 1];
// pop to its parent view controller with NO animation
[self.navigationController popToViewController:parent animated:NO];
// push the target from its parent
[self.navigationController pushViewController:target animated:YES];
return;
}
}
Back to the root view controller with push
case 5VC=>1VC:
UIViewController *root = self.navigationController.viewControllers.firstObject;
// reset view controllers stack with self as root.
[self.navigationController setViewControllers:#[self] animated:NO];
// push target from self
[self.navigationController pushViewController:root animated:YES];
// reset navigation stack with target as root.
[self.navigationController setViewControllers:#[root] animated:NO];
Push new VC from one of ancestors
case 5VC=>4VC
NSArray *vcs = self.navigationController.viewControllers;
for(NSInteger i = vcs.count - 1; i >= 0; i--) {
// find the parent view controller
if([vcs[i] isKindOfClass:[ViewController3 class]]) {
UIViewController *parent = vcs[i];
// pop to the parent with NO animation
[self.navigationController popToViewController:parent animated:NO];
// perform segue from the parent
[parent performSegueWithIdentifier:#"push4VC" sender:self];
return;
}
}
On the particular your case(5VC=>4VC), you know 3VC is the self's parent, you can get the parent directly:
NSArray *vcs = self.navigationController.viewControllers;
UIViewController *parent = vcs[vcs.count - 2]; // [vcs.count-1] is self.
[self.navigationController popToViewController:parent animated:NO];
[parent performSegueWithIdentifier:#"push4VC" sender:self];

UIActivity custom activityViewController crash on iPad

I have a custom UIActivity that I use in order to create a Contact to the device's AddressBook. In this UIActivity, I create an ABNewPersonViewController, put it inside a UINavigationController and return it in UIActivity's
- (UIViewController *)activityViewController
The problem is that on the iPad I get a crash due to a reference to a released UINavigationController. Messages are of type:
*** -[UINavigationController _viewControllerForSupportedInterfaceOrientations]: message sent to deallocated instance 0xa6f1660
Also after my controller is dismissed, the iPad does not autorotate the view when the interface changes orientation.
The code I use is the following:
In UIActivity
- (void)prepareWithActivityItems:(NSArray *)activityItems
{
// Prepare the AB View Controller
ABRecordRef aContact = ABPersonCreate();
CFErrorRef error = NULL;
ABRecordSetValue(aContact, kABPersonKindProperty, kABPersonKindOrganization, &error);
ABRecordSetValue(aContact, kABPersonOrganizationProperty, #"Apple Inc.", &error);
ABMultiValueRef phone = ABMultiValueCreateMutable(kABMultiStringPropertyType);
ABMultiValueAddValueAndLabel(phone, #"+1 2345 784513", kABWorkLabel, NULL);
ABRecordSetValue(aContact, kABPersonPhoneProperty, phone, &error);
CFRelease(phone);
self.newContactVC.title = #"New company";
self.newContactVC.displayedPerson = aContact;
[self.navigation setViewControllers:[NSArray arrayWithObject:self.newContactVC]];
CFRelease(aContact);
}
- (UIViewController *)activityViewController
{
return self.navigation;
}
// Dismisses the new-person view controller.
- (void)newPersonViewController:(ABNewPersonViewController *)newPersonViewController didCompleteWithNewPerson:(ABRecordRef)person
{
[self activityDidFinish:YES];
}
In the ViewController that calls this UIActivity
- (IBAction)showActionsSheet:(id)sender {
AddToAddressBookActivity *abActivity = [[AddToAddressBookActivity alloc] init];
UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:nil
applicationActivities:[NSArray arrayWithObject:abActivity]];
activityVC.excludedActivityTypes = #[UIActivityTypeAssignToContact, UIActivityTypeSaveToCameraRoll];
if (!self.popover || ![self.popover isPopoverVisible]) {
self.popover = [[UIPopoverController alloc] initWithContentViewController:activityVC];
[self.popover setDelegate:self];
self.popover.passthroughViews = nil;
[self.popover presentPopoverFromRect:self.showASBtn.frame
inView:self.view
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
}
}
Any help would be greatly appreciated.
Link to a demo project:
http://ge.tt/23MeOYq/v/0?c
I had a similar problem, except the crash occurred on rotation after the activity view controller was dismissed. In my case, I had the custom view controller in a storyboard setup to present as "fullscreen", and on changing it to display as "form sheet" the crash went away. I don't know why that made a difference though.
Regarding your autorotation not working after the custom view controller dismisses, try adding a completion handler to your activity view controller to nil your popover view controller ivar. I noticed that if I didn't do this the custom activity and the custom view controller don't get dealloc'd, and autorotation doesn't happen. e.g.:
avc.completionHandler = ^(NSString* activityType, BOOL completed){
if ( UIUserInterfaceIdiomPad == [UIDevice currentDevice].userInterfaceIdiom )
self.popover = nil;
};
Also, check out the post at: "activityviewcontroller not dismissing", which I found helpful.

Application unit testing in IOS

I am testing an application in IOS5 using OCUnit. Before posting my problem I read all the relevant posts here as much I could. It really helped. I am now facing the problem below ,which I tried to narrate as clearly as possible.
My application has a login screen which is nothing but a view controller say: initialController. The root controller of my app delegate is the Navigation controller. The property initWithRootViewController of the Navigation controller is set with this initialController.
Now after logging in , the application loads another controller and I want to do unit testing in that controller without having to go through this login process.
Post login, another controller say PostloginController gets loaded, this controller's Navigation item is customized with 2 button's say: button1 and button2.
These buttons are added as the subviews of UIBarButtonItem and this UIBarButtonItem is set as the rightBarButtonItem of the PostloginController.navigationItem.rightBarButtonItem.(pseudo code)
finally PostLoginController is pushed in the Navigation controller
navigationcontroller pushviewcontroller:postLoginController.(pseudo code)
I have to write unit test code which on button1's UIControlEventTouchUpInside should launch another controller say :Newcontroller.
Loading this new controller on button1's event complete one test case.
My problem is that I don't get the subview of UIBarButtonItem which should be 2 buttons. The log show me there is one controller in the Navigation Controller and only one subview. My code is as follows:
- (void)setUp
{
[super setUp];
// Set-up code here.
self.appDelegate = (MYAppDelegate*)[[UIApplication sharedApplication] delegate ];
self.navigationController = (UINavigationController *)self.appDelegate.window.rootViewController;
}
- (void)testAppDelegate
{
STAssertNotNil(self.appDelegate, #"Cannot find the application delegate");
NSArray *tempArray = [self.navigationController viewControllers];
NSLog(#"Number of controllers in navigationController = %i", [tempArray count]);
}
- (void) testButton1
{
//self.myController = (PostLoginController*)self.navigationController.topViewController;
UIBarButtonItem *barButtonItem = self.navigationController.topViewController.navigationItem.rightBarButtonItem;
UIView *customView = barButtonItem.customView;
NSArray *subviews1 = customView.subviews;
NSLog (# "Got subviews");
NSLog(#"Number of subviews = %i", [subviews1 count]);
if ([subviews1 count] == 0)
{
NSLog (# "no subviews");
}
for (UIView* view in subviews1)
{
NSLog(#"%#", view);
if([view isKindOfClass:[UIButton class]])
{
UIButton *btn = (UIButton*)view;
NSLog (# " UIButton parsed");
// Check if Button1 clicked.
NSData *data1=UIImagePNGRepresentation([btn backgroundImageForState:UIControlStateNormal]);
NSData *data2=UIImagePNGRepresentation([UIImage imageNamed:#"BUTTON1.png"]);
if([data1 isEqualToData:data2]) // CHECKING IF BUTTON1 OR BUTTON2 PRESSED BY IMAGE SINCE THEY DONT HAVE ANY TAGS SET.
{
[btn sendActionsForControlEvents:(UIControlEventTouchUpInside)];
STAssertTrue([self.navigationController.visibleViewController isMemberOfClass: [NewController class]], #" NewController failed to load!"); //BUTTON1 CLICK SHOULD LAUNCH NEWCONTROLLER - COMPLETES ONE TEST CASE.
}
}
}
}

EXC_BAD_ACCESS although objects are not freed?

I have the following problem in my code:
UITableViewController *controller = nil;
switch (indexPath.row) {
case 0:
controller = self.kundenTableViewController;
break;
case 1:
controller = self.projekteTableViewController;
break;
case 2:
controller = self.leistungenTableViewController;
break;
case 3:
controller = self.zeitenTableViewController;
break;
}
[self.navigationController pushViewController:controller animated:YES];
All those four view controllers are properly defined in the .h-file and are synthesized manually (and yes, all are exactly the same, I double checked):
- (LeistungenTableViewController*)leistungenTableViewController {
if (leistungenTableViewController == nil) {
// Neu erzeugen
leistungenTableViewController = [[LeistungenTableViewController alloc] initWithNibName:#"LeistungenListeView" bundle:nil];
}
return leistungenTableViewController;
}
Now, something strange happens: if the case 0: is called, controller becomes self.kundenTableViewController. Then I get an EXC_BAD_ACCESS at the last line, where the view controller is pushed onto the stack. This does only happen with this particular controller, not with the other ones.
I tried NSZombies and checked via NSLog whether the controller gets initialized properly, but everything seems fine. Any ideas?
Update: here's the code for the four controllers:
- (KundenTableViewController*)kundenTableViewController {
if (kundenTableViewController == nil) {
// Neu erzeugen
kundenTableViewController = [[KundenTableViewController alloc] initWithNibName:#"KundenListeView" bundle:nil];
}
return kundenTableViewController;
}
- (LeistungenTableViewController*)leistungenTableViewController {
if (leistungenTableViewController == nil) {
// Neu erzeugen
leistungenTableViewController = [[LeistungenTableViewController alloc] initWithNibName:#"LeistungenListeView" bundle:nil];
}
return leistungenTableViewController;
}
- (ProjekteTableViewController*)projekteTableViewController {
if (projekteTableViewController == nil) {
// Neu erzeugen
projekteTableViewController = [[ProjekteTableViewController alloc] initWithNibName:#"ProjekteListeView" bundle:nil];
}
return projekteTableViewController;
}
- (ZeitenTableViewController*)zeitenTableViewController {
if (zeitenTableViewController == nil) {
// Neu erzeugen
zeitenTableViewController = [[ZeitenTableViewController alloc] initWithNibName:#"ZeitenListeView" bundle:nil];
}
return zeitenTableViewController;
}
I just can't figure out why it only happens with the first one.
Sounds like something goes wrong with intialization of the nib file, in particular once the -loadView method which is called (which happens just before the view controller is displayed and is responsible for hooking up the IBOutlets with the proxy objects). Are you sure every IBOutlet is properly connected and the view is connected as well? You might want to check your nibs.

Releasing "referential" variables in method scope

In objective-c (cocoa touch) I have a series of UIViewControllers that I am switching between.
- (void)switchViews:(id)sender
{
UIButton *button = (UIButton *)sender;
UIViewController *nextViewController;
int tag = button.tag;
switch (tag)
{
// -- has already been created
case kFinancialButton:
nextViewController = financials;
break;
case kSocialButton:
if (social == nil)
{
SocialViewController *socialViewController = [[SocialViewController alloc] initWithNibName:#"SocialViewController" bundle:nil];
social = socialViewController;
[socialViewController release];
}
nextViewController = social;
break;
case kTicketingButton:
if (ticketing == nil)
{
TicketingViewController *ticketingViewController = [[TicketingViewController alloc] initWithNibName:#"TicketingViewController" bundle:nil];
ticketing = ticketingViewController;
[ticketingViewController release];
}
nextViewController = ticketing;
break;
}
///////
------> // -- [button/nextViewController release]????
///////
[self setActiveButton:button];
}
As you can see, I'm assigning one of the view controllers to "nextViewController". What I'm wondering is if I need to release this "local" variable or if it's ok to leave alone since it's just pointing to one of my view controllers (which I release in dealloc). I don't think "tag" needs to be released since it's a "primitive", correct? How about button? I don't quite understand what should and shouldn't be released explicitly so perhaps I'm being overcautious. Thanks in advance.
In general you only have to release a variable that you've retain'd init'd or copy'd.
Edit:
After reading your code a little bit more, it seems like you'd have other issues with bad values. The below code makes a little more sense to me. This assumes that financials, social, and ticketing are all #synthesized ivars.
- (void)switchViews:(id)sender
{
UIButton *button = (UIButton *)sender;
UIViewController *nextViewController;
int tag = button.tag;
switch (tag)
{
// -- has already been created
case kFinancialButton:
nextViewController = self.financials;
break;
case kSocialButton:
if (!social) {
self.social = [[[SocialViewController alloc] initWithNibName:#"SocialViewController" bundle:nil] autorelease];
}
nextViewController = self.social;
break;
case kTicketingButton:
if (!ticketing) {
self.ticketing = [[[TicketingViewController alloc] initWithNibName:#"TicketingViewController" bundle:nil] autorelease];
}
nextViewController = self.ticketing;
break;
}
// Do something with nextViewController I'd assume
[self setActiveButton:button];
}