I'm having a main view controller containing a UIScrollView called containerScrollView. This scrollview has on each page another scrollview with the size of the screen containing two view controllers: MessagesViewController and InfoViewController. Here's a schema.
The personScrollView in the containerScrollView works fine but the problem is in the adding of the two view controllers' view to the personScrollView.
#property (nonatomic, retain) MessagesViewController *matchesVC;
#property (nonatomic, retain) InfoViewController *standingsVC;
for (int i = 0; i < 3; i++) {
UIScrollView *personScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(i*320, 0, 320, self.containerScrollView.frame.size.height)];
NSArray *colors = #[[UIColor blueColor], [UIColor orangeColor], [UIColor greenColor]];
[personScrollView setBackgroundColor:[y objectAtIndex:i]];
[personScrollView setPagingEnabled:YES];
[personScrollView setContentSize:CGSizeMake(self.view.frame.size.width * 2, personScrollView)];
[self.containerScrollView addSubview:personScrollView];
/* Populate the scrollview */
// Messages
if (self.messagesVC == nil)
{
self.messagesVC = [[MessagesViewController alloc] init];
[self.messagesVC setFrame:CGRectMake(0, 0, 320, self.containerScrollView.frame.size.height)];
}
[personScrollView addSubview:self.messagesVC.view];
// Info
if (self.infoVC == nil)
{
self.infoVC = [[InfoViewController alloc] init];
[self.infoVC setFrame:CGRectMake(320, 0, 320, self.containerScrollView.frame.size.height)];
}
[personScrollView addSubview:self.infoVC.view];
}
[self.containerScrollView setContentSize:CGSizeMake(320*3, self.containerScrollView.frame.size.height)];
The problem is that the two view controllers (messages and info) only get added once, and to the last personScrollView of containerScrollView.
How to get the view controllers added to all of my personScrollViews? Something wrong with the property declaration?
I have read something about this abusing view controllers, but this is the only solution. There is really a lot of code in the two view controllers and I can't add it to my rootviewcontroller.
The problem is with your understanding of the difference between view controllers and views. You need to read up on Creating Custom Container View Controllers.
Apple doc says:
Views can have only one superview. If view already has a superview and that view is not the receiver, this method removes the previous superview before making the receiver its new superview.
You create your controllers once, but you want to add theirs views three times, to three different parent views. You can't do that.
I ended up creating multiple instances of my view controllers and storing them in an array. Not a great solution, but the best I could find.
#property (strong, nonatomic) MessagesViewController *messagesVC1;
#property (strong, nonatomic) MessagesViewController *messagesVC2;
#property (strong, nonatomic) MessagesViewController *messagesVC3;
#property (strong, nonatomic) MessagesViewController *messagesVC4;
#property (strong, nonatomic) MessagesViewController *messagesVC5;
#property (strong, nonatomic) MessagesViewController *messagesVC6;
self.messagesVC1 = [[MessagesViewController alloc] initWithData:data];
self.messagesVC2 = [[MessagesViewController alloc] initWithData:data];
self.messagesVC3 = [[MessagesViewController alloc] initWithData:data];
self.messagesVC4 = [[MessagesViewController alloc] initWithData:data];
self.messagesVC5 = [[MessagesViewController alloc] initWithData:data];
self.messagesVC6 = [[MessagesViewController alloc] initWithData:data];
self.messagesVCArray = #[self.messagesVC1, self.messagesVC2, self.messagesVC3, self.messagesVC4, self.messagesVC5, self.messagesVC6];
MessagesViewController *messagesVC = [self.messagesVCArray objectAtIndex:i];
[messagesVC setFrame:CGRectMake(0, 0, 320, leagueScrollView.frame.size.height)];
[leagueScrollView addSubview:messagesVC.view];
Related
I'm trying to create a basic project that uses labels and gesture recognition but am having a little difficulty. I created an interface of 4 UILabels positioned vertically along the center of the app screen. Looks something like:
Question
Next Question
Answer
Show Answer
...just 4 labels, positioned one after the next. I make connections from Interface Builder to my ViewController.h file to create IBOutlet properties for each of the labels. Simple enough. I then go ahead and write all the implementation code in the .m file (seen below). The only issue is when the app runs, all that is displayed is four positioned labels with the default "Label" text from Interface Builder. The gesture recognizers I added to two of the labels do not seem to be connected/triggering either.
Hoping someone could shed a little light on the issue for me. What started as a simple exercise is driving me a little mad.
Here's my Viewcontroller.h file:
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
#property (nonatomic, strong) IBOutlet UILabel *labelQuestion;
#property (nonatomic, strong) IBOutlet UILabel *labelNextQuestion;
#property (nonatomic, strong) IBOutlet UILabel *labelAnswer;
#property (nonatomic, strong) IBOutlet UILabel *labelShowAnser;
#end
And here's my Viewcontroller.m file:
#import "ViewController.h"
#interface ViewController ()
#property (nonatomic, copy) NSArray *arrayQuestions;
#property (nonatomic, copy) NSArray *arrayAnswers;
#property (assign) int incrementer;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// First create the datasource arrays with five elements each
self.arrayQuestions = [[NSArray alloc] init];
self.arrayQuestions = [NSArray arrayWithObjects:#"Who am I?", #"Who are you?", #"Where are we?", #"What's going on?", #"Why is this happening?", nil];
self.arrayAnswers = [[NSArray alloc] init];
self.arrayAnswers = [NSArray arrayWithObjects:#"Damian", #"Dmitri", #"I don't know.", #"You tell me.", #"I have no clue", nil];
// Reset the incrementer's value
self.incrementer = 0;
// Next configure the labels
self.labelQuestion = [[UILabel alloc] init];
self.labelQuestion.text = [self.arrayQuestions objectAtIndex:self.incrementer];
self.labelNextQuestion = [[UILabel alloc] init];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(nextQuestionLabelPressed)];
[tap setNumberOfTapsRequired:1];
[self.labelNextQuestion addGestureRecognizer:tap];
self.labelNextQuestion.text = #"Show next question";
self.labelAnswer = [[UILabel alloc] init];
self.labelAnswer.text = #"?????";
self.labelShowAnser = [[UILabel alloc] init];
self.labelShowAnser.userInteractionEnabled = YES;
UITapGestureRecognizer *tapp = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(showAnswerLabelPressed)];
[tapp setNumberOfTapsRequired:1];
[self.labelShowAnser addGestureRecognizer:tapp];
self.labelShowAnser.text = #"Show answer";
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)nextQuestionLabelPressed {
self.incrementer++;
self.labelQuestion.text = [self.arrayQuestions objectAtIndex:self.incrementer];
self.labelAnswer.text = #"?????";
}
- (void)showAnswerLabelPressed {
self.labelAnswer.text = [self.arrayAnswers objectAtIndex:self.incrementer];
}
#end
Let's say you've connected a label from the storyboard to self.labelQuestion. So far so good. When you launch, self.labelQuestion points to the label from the storyboard, which is the one that you see in the visible interface of the running app.
But then you say:
self.labelQuestion = [[UILabel alloc] init];
That disconnects the outlet and replaces the value of that variable (self.labelQuestion) with a new blank label! So anything you now do to labelQuestion cannot possibly affect the one from the storyboard, as you have broken the connection.
You do that for all four labels, so you have smashed all four outlets into dust. Thus, your other code has no effect on the visible labels, which come from the storyboard.
Just delete all four [[UILabel alloc] init] lines, and all will be well.
I have some authentication view controllers that use my begin navigation controller, like so in AppDelegate.m:
#property (strong, nonatomic, retain) BeginViewController *beginViewController;
#property (strong, nonatomic, retain) BeginNavigationController *beginNavigationController;
...
_beginViewController = [[BeginViewController alloc] init];
_beginNavigationController = [[BeginNavigationController alloc] initWithRootViewController:_beginViewController];
self.window.rootViewController = _beginNavigationController;
[self.window makeKeyAndVisible];
After a certain point, the user will autnenicate themselves and if there are no errors this happens:
ATabBarController *tab = [[ATabBarController alloc] init];
[self.navigationController pushViewController:tab animated:YES];
Then a tab bar controller init's:
#property (nonatomic, strong, retain) InsideNavigationController *nav1;
....
#property (nonatomic, strong, retain) InsideNavigationController *nav4;
_view1 = [[AViewController alloc] init];
...
_view2 = [[DViewController alloc] init];
_nav1 = [[InsideNavigationController alloc]initWithRootViewController:_view1];
...
_nav4 = [[InsideNavigationController alloc]initWithRootViewController:_view4];
Is this the proper way to do things? I wanted to have a navigation controller handle authentication and all unauthenticated views and then a navigation contoller for the tab bar after authenticating.
How do I get back to the first view controller?
Thanks.
I am trying to implement an UIPickerView programmatically. I have implemented the delegate and datasource.
When I first navigate to the UIPickerView everything works fine. If I leave the View and come back to it later, the UIPickerView looks fine.
But when I try to select an other item it crashes.
When I debug it, I saw that my data array is empty. But I don’t know why.
In on class I init the UIPickerView:
DropDownController *objPickerView = [[DropDownController alloc] init];
objPickerView.userInfo = userInfo;
[objPickerView setDataSourceForPickerView:[dropDownItem valueForKey:#"dropDownEntries"] withPreselectedItem:preSelectedItem];
[dropDownContainer addSubview:objPickerView.picker];
The Picker is in this Controller:
#interface DropDownController : UIViewController <FormElement, UIPickerViewDelegate, UIPickerViewDataSource>
{
NSArray *dropDownData;
UIPickerView *picker;
UIElement *userInfo;
}
#property (strong, nonatomic) NSArray *dropDownData;
#property (strong, nonatomic) IBOutlet UIPickerView *picker;
#property (nonatomic, retain) UIElement *userInfo;
-(void)setDataSourceForPickerView:(NSArray *)dataDictionary withPreselectedItem:(NSString*) preSelectedItem;
#end
Here I set the Delegate and Datasource:
-(void)setDataSourceForPickerView:(NSMutableArray *)dataDictionary withPreselectedItem:(NSString*) preSelectedItem{
picker = [[UIPickerView alloc] initWithFrame:CGRectMake(0, 0, 300, 162)];
picker.delegate = self;
picker.dataSource = self;
picker.showsSelectionIndicator = YES;
dropDownData = dataDictionary;
}
After returning to the View dropDownData is empty.
I've found the problem. I need to copy the dictionary, not only set a reference.
// dropDownData = dataDictionary; -> wrong
dropDownData = [dataDictionary copy];
So I'm trying to add a viewController to a tabBarController that should force the moreViewController since I have 5 already and I would be adding a 6th. I'm setting the viewControllers property of the tabBarController using an array literal. This seems like something that's really simple, but I can't seem to find anything on stack or in Apple's docs that say it isn't possible to initialize it like this.
Here's the code below and the output of the NSLog's when I was trying to debug the issue. I can't figure out why I can initialize the viewControllers property to be 6 objects instead of 5 like it's showing:
#property (nonatomic, retain) UINavigationController* navVC1;
#property (nonatomic, retain) UINavigationController* navVC2;
#property (nonatomic, retain) UINavigationController* navVC3;
#property (nonatomic, retain) UINavigationController* navVC4;
#property (nonatomic, retain) UINavigationController* navVC5;
#property (nonatomic, retain) UINavigationController* navVC6;
#property (nonatomic, retain) customVC1* vc1;
#property (nonatomic, retain) customVC2* vc2;
#property (nonatomic, retain) customVC3* vc3;
#property (nonatomic, retain) customVC4* vc4;
#property (nonatomic, retain) customVC5* vc5;
#property (nonatomic, retain) customVC6* vc6;
self.vc1 = [customVC1 loadFromNib];
self.vc2 = [customVC2 loadFromNib];
self.vc3 = [customVC3 loadFromNib];
self.vc4 = [customVC4 loadFromNib];
self.vc5 = [customVC5 loadFromNib];
self.vc6 = [customVC6 loadFromNib];
self.navVC1 = [[[CustomNavController alloc] initWithRootViewController:self.vc1] autorelease];
self.navVC2 = [[[CustomNavController alloc] initWithRootViewController:self.vc2] autorelease];
self.navVC3 = [[[CustomNavController alloc] initWithRootViewController:self.vc3] autorelease];
self.navVC4 = [[[CustomNavController alloc] initWithRootViewController:self.vc4] autorelease];
self.navVC5 = [[[CustomNavController alloc] initWithRootViewController:self.vc5] autorelease];
self.navVC6 = [[[CustomNavController alloc] initWithRootViewController:self.vc6] autorelease];
self.tabBarController = [[[UITabBarController alloc] init] autorelease];
self.tabBarController.viewControllers = #[self.navVC1, self.navVC2, self.navVC3, self.navVC4, self.navVC5, self.navVC6];
NSArray* tBArray = #[self.navVC1, self.navVC2, self.navVC3, self.navVC4, self.navVC5, self.navVC6];
NSLog(#"Here's the number of VCs %d",[self.tabBarController.tabBar.items count]); // this outputs 5
NSLog(#"Here's the array count %d",[tBArray count]); // this outputs 6
Here's an excerpt from another method where I style the tabBar:
int index = 0;
UITabBarItem* tb = nil;
tb = [self.tabBarController.tabBar.items objectAtIndex:index++];
[tb setTitle:#"vc1"];
tb = [self.tabBarController.tabBar.items objectAtIndex:index++];
[tb setTitle:#"vc2"];
tb = [self.tabBarController.tabBar.items objectAtIndex:index++];
[tb setTitle:#"vc3"];
tb = [self.tabBarController.tabBar.items objectAtIndex:index++];
[tb setTitle:#"vc4"];
tb = [self.tabBarController.tabBar.items objectAtIndex:index++];
[tb setTitle:#"vc5"];
tb = [self.tabBarController.tabBar.items objectAtIndex:index++]; // this is the out of bounds crash
[tb setTitle:#"vc6"];
So the app is crashing on me and here's the out of bounds error:
*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 5 beyond bounds [0 .. 4]'
EDIT: Here's the solution:
Thanks for the help everyone. Figured out that can't apply attributes directly to the tabBarItems using objectAtIndex once have more than 5 tabs since it seems the 5th tab once you go over 5 becomes an array of viewControllers which is the moreViewController. I just rewrote the code to apply all attributes directly to the viewControllers before adding them to the tabBar as follows:
UIImage* vc1TabIconSelected = [[UIImage imageNamed:#"tabBarIconVC1Selected.png"]
imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
self.savedTabViewController.tabBarItem = [[UITabBarItem alloc] initWithTitle:#"VC1"
image:[UIImage imageNamed:#"tabBarIconVC1.png"]
selectedImage:vc1TabIconSelected];
[self.vc1TabViewController.tabBarItem setTitleTextAttributes:#{ UITextAttributeTextColor : [UIColor colorFromHex:#"424242"] } forState:UIControlStateSelected];
Even though there are 6 view controllers in viewControllers, tabBar still only has 5 items.
From the UITabBarController documentation:
You should never access the tab bar view of a tab bar controller directly. To configure the tabs of a tab bar controller, you assign the view controllers that provide the root view for each tab to the viewControllers property.
Set the view controllers' title property; don't access tabBar directly.
UITabBarController can only feature a maximum of 5 tabs. If you assign more than 5 view controllers to viewControllers the tab bar will only shows the first 4 tabs and a 5th tab called More, which shows a table view with rows for each remaining tab. The point here is, in this case, the UITabBarController shows that it has the full number of view controllers, while its UITabBar shows a maximum of 5 items.
[tabBarController.tabBar.items objectAtIndex:5] is what made your app crash.
Not sure why that does not work, but you could also use
[self.tabBarController addChildViewController:viewController];
for each one
I am working on the UIPopover and in one of the example I found that the Popover object is
created but then the object is assigned to the property of the Viewcontroller.
UIPopoverController* aPopover = [[UIPopoverController alloc] initWithContentViewController:content];
self.popoverController = aPopover;
What is the merit in such assignment and any reason for not assigning object to the property directly?
There is no "merit" in it. Saying
self.popoverController =
[[UIPopoverController alloc] initWithContentViewController:content];
would be absolutely equivalent.
On the other hand there is nothing wrong with using a temporary variable (aPopover) as shown in your example. It's just a name (a pointer); there is no significant waste of space or time. Moreover, saying self.popoverController repeatedly (either to set or to get its value) is to be avoided, because this is a method call - you are passing through the setter method or getter method (which may be synthesized, may have side effects, and does in fact take some extra time). Thus, when there is much configuration to be done (for example), it is best to do it as shown in your example:
UIPopoverController* aPopover =
[[UIPopoverController alloc] initWithContentViewController:content];
// lots of configuration here, using the local automatic variable aPopover...
// ...and then, only when it is all done, call the setter:
self.popoverController = aPopover;
The only reason for that is that you probably read this in a tutorial somewhere. And the author did it for readability for beginners. You could absolutely use:
self.popoverController = [[UIPopoverController alloc] initWithContentViewController:content];
All depending on how familiar you are with programming in general and how readable you want your code to be.
I agree with the others that, in this case, the assigning of the popover controller to a local variable before later assigning it to a class property is largely a stylistic matter. But this is only the case because you are keeping a strong reference to that popover controller. There are other situations where you have weak properties, in which this local variable pattern is critical.
For example, let's assume that we have a bunch of controls that we're going to add to our view controller's view programmatically:
#property (nonatomic, strong) UIView *containerView;
#property (nonatomic, strong) UILabel *usernameLabel;
#property (nonatomic, strong) UILabel *emailLabel;
// and more labels
When you want to add these to your view controller's view, you could do something like:
- (void)addSubviewsAtPoint:(CGPoint)location
{
self.containerView = [[UIView alloc] initWithFrame:[self frameForContainer:location]];
[self.view addSubview:self.containerView];
self.usernameLabel = [[UILabel alloc] initWithFrame:[self frameForUsernameLabel]];
[self.containerView addSubview:self.usernameLabel];
self.usernameLabel.text = self.username;
self.emailLabel = [[UILabel alloc] initWithFrame:[self frameForEmailLabel]];
[self.containerView addSubview:self.emailLabel];
self.emailLabel.text = self.email;
// etc.
}
But it also means that when you remove the subviews, you not only have to remove the container view from your view hierarchy, but you also have to remember to nil all of the properties for all of those subviews:
- (void)removeSubviews
{
[self.containerView removeFromSuperview];
self.containerView = nil;
self.emailLabel = nil;
self.usernameLabel = nil;
// etc.
}
This introduces a maintenance issue, that every time you add a new control via addSubviewsAtPoint, that you also have to remember to add it to removeSubviews, too, or else you might be hanging on to the control well after you've removed it from the screen.
To simplify your life, you might make all of these properties weak (with the intuition being that it's the view that owns these subviews, not the view controller):
#property (nonatomic, weak) UIView *containerView;
#property (nonatomic, weak) UILabel *usernameLabel;
#property (nonatomic, weak) UILabel *emailLabel;
// etc.
But now, using ARC, your addSubviewsAtPoint no longer works, because when you assign an object to a weak property, if there are no other strong references, it will be immediately become nil:
self.containerView = [[UIView alloc] initWithFrame:[self frameForContainer:location]];
[self.view addSubview:self.containerView]; // FAIL!!! self.containerView will be nil!
So, instead, we employ that local variable pattern of your question to ensure that the controls are not prematurely deallocated while we're adding them to our view:
- (void)addSubviewsAtPoint:(CGPoint)location
{
UIView *containerView = [[UIView alloc] initWithFrame:[self frameForContainer:location]];
[self.view addSubview:containerView];
self.containerView = containerView;
UILabel *usernameLabel = [[UILabel alloc] initWithFrame:[self frameForUsernameLabel]];
[containerView addSubview:usernameLabel];
usernameLabel.text = self.username;
self.usernameLabel = usernameLabel;
UILabel *emailLabel = [[UILabel alloc] initWithFrame:[self frameForEmailLabel]];
[containerView addSubview:emailLabel];
emailLabel.text = self.email;
self.emailLabel = emailLabel;
// etc.
}
And, as a result, because we're using weak properties, our removal of those subviews is now much simpler, as we don't have to nil all of those properties when we remove the containerView from our view controller's view:
- (void)removeSubviews
{
[self.containerView removeFromSuperview];
// because all of those `containerView` subviews were `weak`,
// we don't have to manually `nil` them
}