Multiple objects with NIB in NSMutableArray - objective-c

I have a noob-question, that might be similar to Dynamically create multiple instances of a UIView from a NIB but I'm missing the big picture.
I've got a class with NIB, called UserViewController and I need to create multiple instances, based on the number of users, store them in an array and be able to modally navigate between them.
A class with NIB, called SelectNumberOfUsersViewController contains this IBACTION-code:
users = [[NSMutableArray alloc] init];
for (int i=0; i<numberOfUsers; i++) {
user = [[UserViewController alloc] init];
user.userid = i+1;
[user doInitialization];
[users addObject:user];
}
I see that the initWithNibName of the instance user is run, but how do I address and show the UI for the first user in the users array?
I'm not sure if commands like
myView = [[[NSBundle mainBundle] loadNibNamed:#"XXX" owner:self options:nil] objectAtIndex:0];
[[self view] addSubview:searchDateView]
should be used, since the array contains entire objects of the class User with NIB and everything - or...?

If you want to initialise a viewcontroller with a nib file you should use:
user = [[UserViewController alloc] initWithNibName:#"NibName" bundle:nil];
If you want to push that view you could call:
[self.navigationController pushViewController:user animated:true];
To present it, call:
[self presentModalViewController:user animated:true];
If you just want to add the view to the current viewcontroller use:
[self.view addSubView:user.view];
But the sure to remove the previous one too.
I hope this was of any help.

Related

How do I insert UITextField in to UITableView after button is pressed

In master view application xcode generates ready app with table view and the plus button. I want to change that button to to add a new cell but not with the date as it is by default. I want to add two text fields like label->textfield, label->textfield.
In code I have this:
- (void)viewDidLoad{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.navigationItem.leftBarButtonItem = self.editButtonItem;
UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(insertNewObject:)];
self.navigationItem.rightBarButtonItem = addButton;
self.detailViewController = (GCDetailViewController *) [[self.splitViewController.viewControllers lastObject] topViewController];
}
and the function:
- (void)insertNewObject:(id)sender{
if (!_objects) {
_objects = [[NSMutableArray alloc] init];
}
[_objects insertObject:[UITextField alloc] atIndex:0];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
Thank You
The way to think about this is model-view-controller (MVC). _objects is your model representing whatever the user thinks is in the table. Say it's a to-do list, then objects could be an array of NSObject subclass you create like TodoItem.
You would insert new TodoItems into _objects, then tell your table (the "View" in MVC) that it's model has changed. You can do that imprecisely using reloadData, or in a more targeted fashion as your code suggests, calling insertRowsAtIndexPaths - but that call must be sandwiched between tableView beginUpdates and endUpdates.
You can add textFields in code in your cellForRowAtIndexPath, or in the cell prototype in storyboard. Your table view datasource should always refer to objects... i.e. numberOfRows answers self.objects.count, cellForRowAtIndexPath gets:
TodoItem *item = [self.objects objectAtIndexPath:indexPath.row];
and uses that item's properties to initialize the textField's text. Also, incidentally, objects should be declared like this:
#property(strong,nonatomic) NSMutableArray *objects;
...and your code should refer to self.objects almost everywhere (not _objects). Initializing it on the first insert is too late, because the table needs it to be valid right-away, as soon as it's visible. Usually, a good practice is a "lazy" init replacing the synthesized getter...
- (NSMutableArray *)objects {
if (!_objects) { // this one of just a few places where you should refer directly to the _objects
_objects = [NSMutableArray array];
}
return _objects;
}
You might find using the free Sensible TableView framework really helpful here. Here is some sample code to illustrate how you'd do this using the framework:
- (void)insertNewObject:(id)sender{
SCTableViewSection *section = [self.tableViewModel sectionAtIndex:0];
[section addCell:[SCTextFieldCell cellWithText:#"Enter Text"]];
}
Comes in really handy for these types of situations.

Use xib without a view controller

I need to create several similar views
In a easy way,I create some views in xib(each full screen)
And I have a view controller to use this xib's views,code like this:
NSArray* views = [[NSBundle mainBundle] loadNibNamed:#"MyXibName" owner:nil options:nil];
[self.view addSubview:[views objectAtIndex:aIndex]];
At this moment,view shows alright.
Now,there's some buttons in those views,so I connect a outlet for each view
bad thing happened
app crashed due to
uncaught exception 'NSUnknownKeyException', reason: '[<NSObject 0x969db50> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key
Analyse:
Although my xib file's "File Owner" has been set,but there's no connection between xib and the only view controller.
How could I get the pointer of a view's button?
You can do it like this:
NSNib* aNib = [[NSNib alloc] initWithNibNamed:#"MyGreatNib" bundle:nil];
NSArray* topLevelObjs = nil;
for (SomeClass *obj in myOwnerObjects) {
topLevelObjs = nil;
if (![aNib instantiateNibWithOwner:obj topLevelObjects:&topLevelObjs])
{
NSLog(#"Warning! Could not load nib file.\n");
return;
}
for (id topLevelObj in topLevelObjs) {
if ([topLevelObj isKindOfClass:[NSView class]]) {
NSView *otView = (NSView *)topLevelObj;
// set frame...
[self addSubview:otView];
}
}
}
Oops...
I just found something.
UINib* xib = [UINib nibWithNibName:#"MyXibName" bundle:nil];
UIView* view = [[xib instantiateWithOwner:self options:nil] lastObject];
It works!
You can design xib as per your need by defining class UIView
Code in .m file:
NSArray* objects = [[NSBundle mainBundle] loadNibNamed:#"Interface" owner:nil options:nil];
UIView* mainView = [objects objectAtIndex:0];
for (UIView* view in [mainView subviews]) {
if([view isKindOfClass:[UILabel class]])
{
UILabel* label = (UILabel*)view;
//....As You Wish...
}
}
Default implementation of -loadView creates the view or loads NIB. As far as I know, there is no way to know the final size of the view at time of creation in -loadView. So the default view size is set to [[UIScreen mainScreen] bounds]. This is because it may be difficult to work work with zero frame view in -viewDidLoad and other methods.
Your one-line implementation may look like this:
- (void)loadView {
self.view = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
}
You don't need to set the autoresizing mask, because you don't know in what context the view will be displayed. The caller is responsible to set you correct frame, autoresizing mask and similar external attributes (I call them like this).
Imagine this in a UINavigationController method:
// we are pushing new VC, view is accessed for the first time
pushedVC.view.frame = CGRectMake(...);
It is setting the correct frame, but your -loadView is called just before that -setFrame:. So during -viewDidLoad you have temporary non-zero frame, just to be able to setup subviews and internal autoresizing. After this, the correct frame is set to you and in -viewWillAppear: you have final frame.

Making an UILabel appear in another view once pressed in one view

I have 2 views
SoundViewController
ShowViewController
The sound view has a sound on it (IBAction).
- (IBAction)oneSound:(id)sender; {
if (oneAudio && oneAudio.playing) {
[oneAudio stop];
[oneAudio release];
oneAudio = nil;
return;
}
NSString *path = [[NSBundle mainBundle] pathForResource:#"1k" ofType:#"mp3"];
if (oneAudio) [oneAudio release];
NSError *error = nil;
oneAudio = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:&error];
if (error)
NSLog(#"%#",[error localizedDescription]);
oneAudio.delegate = self;
[oneAudio play];
mainText.text =#"test";
}
And the ShowViewController needs to display the uilabel thats been pressed from the sound button
I want it so once the user has pressed the sound on SoundViewController, the uilabel appear on the showviewcontroller as it appear on the soundviewcontroller at the moment
Well, you can do this by
retain the UILabel
remove it from it's superview
add it to the other view
release it
You'll need access to the ShowViewController from the SoundViewController. So you'll have to define a connection between the two views (via IBOutlet or retained property, most likely).
I'm not sure what variable in the above code is your UILabel, so replace 'sender' with the correct ivar (mainLabel, maybe?):
[sender retain];
[sender removeFromSuperview];
[showViewController.view addSubview:sender];
[sender release];
Edit:
To clarify, the variable in the above code "sender" is the object that triggered this method. Whatever you have connected to the IBAction in the nib. In this case it would probably be a UIButton. You probably have to add an IBOutlet for your UILabel, and attach it to the correct UILabel in your nib file and use that IBOutlet in place of "sender".
You should probably read up on view hierarchy and view controllers. What you're trying to do is remarkably easy, and there are about a dozen ways to make it happen, but you have to understand how to structure your app correctly first. The most obvious issue is that the two view controllers need to have a reference to each-other in order to pass views back and forth. I can't send a view to another view if I don't know where that other view is. The views can be connected in IB, in code when they are created, etc.
view is a property of UIViewController. Assuming your ShowViewController is a subclass of UIViewController, it will have a view property. Perhaps your showViewController ivar isn't correctly typed? (if the type is id for example, it will give a warning when you try to access it's view property).

Passed array is not passed by popViewControllerAnimated... why?

Dont be put off by the huge question... (its mostly code).
OK, I have a navigation Controller which contains a view controller (Called AddClaim) containing a tableView.
if a cell is selected, this is called:
EditClaimDetails *detailViewController = [[[EditClaimDetails alloc] init] autorelease];
// Pass the selected object to the new view controller.
detailViewController.selectedIndexPath = indexPath;
detailViewController.newClaimArrayDetails2 = newClaimArrayDetails;
[self.navigationController pushViewController:detailViewController animated:YES ];
This works nicely and a new view controller is shown containing a tableView (It is an exclusive list).
In ViewDidLoad of the EditClaimDetails this code exists: (claimTypeHoldingArray is a mutable array declared in the header file)
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:#"Back" style:UIBarButtonItemStyleBordered target:self action:#selector(pressedBack)];
self.navigationItem.leftBarButtonItem = backButton;
claimTypeHoldingArray = [[NSMutableArray alloc] initWithArray:newClaimArrayDetails2];
Basically the result of this is as expected: A back button is shown - when pressed - it calls a selector popping the view controller to AddClaim, claimTypeHoldingArray contains the newClaimsArray given in AddClaim.
This is part of the code in didSelectRowAtIndexPath: (claimTypeArray is the array which holds the textLabels of the cells)
[claimTypeHoldingArray replaceObjectAtIndex:0 withObject:[claimTypeArray objectAtIndex:indexPath.row]];
What this does is that the first object of claimTypeHoldingArray is replaced with what text was on the TextLabel of the cell. so far so good. (tested with nslog)
This is the code for when the back button is pressed:
-(IBAction)pressedBack {
AddClaim *sender = [[[AddClaim alloc] init] autorelease];
sender.newClaimArrayDetails = claimTypeHoldingArray;
[self.navigationController popViewControllerAnimated:YES];
This is where the trouble starts...
This action (according to me) should replace newClaimArrayDetails with claimTypeHoldingArray. (it does so) But when the view controller is popped and the screen moves back to add claim this array is not changed!
What have I done wrong?! btw, all properties are set.
this is the test i do in viewDidAppear:
NSLog(#"%#",[newClaimArrayDetails objectAtIndex:0]);
This answer is the same scale as the question, hope its's not too large ;)
So, in your pressedBack button method you're trying to update the initial AddClaim view controller object with the claimTypeHoldingArray.
You're halfway right - you're definitely updating an AddClaim object, just not the one that's inside your navigation controller. you're creating a new one and updating that instead!
-(IBAction)pressedBack {
// This line creates a new AddClaim view controller
AddClaim *sender = [[[AddClaim alloc] init] autorelease];
// This line updates your _new_ AddClaim view controller
sender.newClaimArrayDetails = claimTypeHoldingArray;
// This line displays the _old_ AddClaim object
[self.navigationController popViewControllerAnimated:YES];
You need to pass into your EditClaimDetails view controller the AddClaim view controller that created it.
In your cell is selected method add something like
detailViewController.addClaimViewController = self;
(where addClaimViewCOntroller is a property on your EditClaimDetails object like
#property (nonatomic, retain) Addclaim *addClaimViewController;
Then, your pressedBack method becomes
-(IBAction)pressedBack {
// This line updates your _old_ AddClaim view controller
addClaimViewController.newClaimArrayDetails = claimTypeHoldingArray;
// This line displays the _old_ AddClaim object
[self.navigationController popViewControllerAnimated:YES];
Hope that helps.
Check your array property definition in AddClaim, is it by any chance (nonatomic, copy)? If yes the it would hold a private copy of your array, so that you original array couldn't change.

Help shortening my code in didSelectRowAtIndexPath into a much easier code

Hey developers!
I built a Table View, populated it with array objects and I implemented a code when a user clicks an object inside my Table View, it loads that object's own nib file, and I am using an if statement. (just a little note, I populated my Table View with 40 objects because that's how much things I have and need), so I'll show only the first two if statements I created at the beginning of my if statement code since I have way too many if statements for all 40 objects in my Table View:
if ([[glossaryArray objectAtIndex:indexPath.row] isEqual:#"Title"]) {
Title *titleLoad = [[Title alloc] initWithNibName:#"Title" bundle:nil];
[self.navigationController pushViewController:titleLoad animated:YES];
[titleLoad release];
}
else if ([[glossaryArray objectAtIndex:indexPath.row] isEqual:#"Meta Description Tag"]) {
MetaDescriptionTag *metaDescriptionTagLoad = [[MetaDescriptionTag alloc] initWithNibName:#"MetaDescriptionTag" bundle:nil];
[self.navigationController pushViewController:metaDescriptionTagLoad animated:YES];
[metaDescriptionTagLoad release];
}
...
So that's it, I don't want to be cocky showing all of my if statements, so yep this code works, each array objects loading it's own separate nib file (40 nib file 1 for each array objects) and each of these nib files are like Web Views, Image Views, Text Fields and other objects. Call me crazy, but this is the only code I can think of right now, so hopefully someone can help me edit this code into a much easier code, thanks
If your values in glossaryArray can be mechanically transformed into the corresponding class/nib name (e.g. by removing all spaces), you could do something like this:
NSString *className = [self classNameFromString:[glossaryArray objectAtIndex:indexPath.row]];
UIViewController *viewController = [[NSClassFromString(className) alloc] initWithNibName:nil bundle:nil];
[self.navigationController pushViewController:viewController animated:YES];
[viewController release];
If they can't be mechanically transformed, you could always construct an NSDictionary mapping from one to the other, or special-case the few that are different. Or rename the classes and nibs.