Loading an array on a sub view with data from main view - objective-c

My application has two views. Main view allows to enter details of a product and stores to an array in the second view. Whenever I switches to second view, all product details should be loaded to a table view.
The problem is that whenever I try to add a product to the array, it saves data but replaces previous data. So all the time array is stored with only one product info. I assume the array is getting initialised every time when I add a product. Here is the code,
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
productArray =[[NSMutableArray alloc] init];
}
return self;
}
I tried to allocate array in viewDidLoad method and many other places but nothing helped.
Please help.
Thanks

I'm not exactly sure if I understand your question, but:
If you want to add an object to an array, you should set the array as NSMutableArray and call the addObject method on it so that the array will now contain your new object.
If you want to pass the array from your main view to your second view, you should do it in your segue setup. Basically you have a #property in your second view, and in the performSegue method in your main view, you set the #property to whatever array you wish it to be.
You cannot use segue without storyboard (at least i don't know how). So if you want to use xib, you need to do it in the traditional way:
SomeViewController *viewController = [[SomeViewController alloc] initWithNibName:#"nib" bundle:nil];;
viewController.array = self.array; // the array you want to pass to your new view
So in your alloc-init thing, you only instantiated your array, but you didn't seem to pass the data to it?

Related

Xcode: How to add Custom View from XIB File in a UIScrollView

Coming from an Android and Java background, I am relative new with Xcode Development.
I am using Xcode 5.0.2 and created my first IOS Application by selecting Create a New Xcode Project -> Single View Application. The initial project structure has been generated and I found that there is a Main_iphone.storyboard file which opens a UI Designer where I can drag and drop items to it. I selected UIScrollView and dragged it into the main window that has been generated.
Now in the Controller header file, I added #property (nonatomic, retain) IBOutlet UIScrollView *scrollView; so that i could access the scrollView from inside my controller code.
I wanted to add an item to the scrollView programatically so I created a template Custom View by adding new file -> Objective-C Class with XIB for User Interface, named it TSTFilesInfoController and designed the XIB by adding a View and a label inside the view. Same with the scrollView above, I created a property to expose the mainView in my controller class.
I hardcoded a loop of 10x inside the controller of the UIScrollView and inside the for loop I am instantiating TSTFilesInfoController and adding the view to the UIScrollView. But when i run the application, nothing is shown or added in the UIScrollView.
Heres the code for adding the CustomView:
- (void)viewDidLoad {
[super viewDidLoad];
for ( int i = 0; i < 10 ; i++) {
TSTFilesInfoController *info = [[TSTFilesInfoController alloc] init];
[self addChildViewController:info];
[self.scrollView addSubview:info.mainView];
NSLog(#"View has been added into the scrollView");
}
}
Can someone please tell me whats wrong with my codes and what would be the correct approach to achieve the output that i wanted? Thank you in advance for the help.
-- EDIT --
This code is auto-generated in TSTFilesInfoController.m
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
This is a misuse of a UIViewController subclass. Do not use a view controller just as a sort of fishing rod to hook a view that's inside a xib file. Simply load the xib file and grab the view yourself, and stuff it into your interface.
There are complex rules for how to put a view controller's view manually inside your interface, and in general it is something you should be reluctant to do. But making views come and go dynamically and directly is easy and common.
Let's suppose the .xib file is called TSTFilesInfo.xib and it has a top-level UIView subclass object, class MyView, which is the one you want. Then:
MyView* v = (MyView*)
([[UINib nibWithNibName:#"TSTFilesInfo" bundle:nil]
instantiateWithOwner:nil options:nil][0]);
This loads the nib once, instantiating its contents, and handing you a reference to that instance (the UIView in this case). Now plunk v into your interface. Keep that reference in an instance variable (probably weak, since it is retained by its superview) so that you can subsequently configure and communicate with any subviews of MyView to which you have created outlets.
NOTE: However, I must say from the example so far that it sounds to me like what you really want here is a UITableView, not a simple UIScrollView. It comes all set to do just the kind of thing you seem to up to here.

The right way of setting up MapKit's delegate in a separate class

What is the proper way of setting up a separate delegate class for MapKit?
I have MapView class subclassing MKMapView and bare MapDelegate class conforming MKMapViewDelegate protocol having only one initializer method.
Here is the extract from MapView initialization method I use:
# MapView.m ...
#implementation MapView
- (id) initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// [self setShowsUserLocation:YES];
[self setDelegate:[[MapDelegate alloc] initWithMapView:self]];
The only method MapDelegate class has is
# MapDelegate.m ...
- (id)initWithMapView:(MapView *)aMapView {
self = [super init];
self.mapView = aMapView;
return self;
}
Having [self setShowsUserLocation:YES]; commented, all works fine - I see the map. If I uncomment this line, my application begins to crash.
What my MapDelegate class is missing?
UPDATE 1: if I don't use a separate class MapDelegate and set just setDelegate:self - all works.
UPDATE 2: Now I understand, that the problem with [self setDelegate:[[MapDelegate alloc] initWithMapView:self]]; string is that I need MapDelegate class to live longer than it does now (delegate property has weak attribute). If I do the following:
#property (strong) id delegateContainer;
....
[self setDelegateContainer:[[MapDelegate alloc] init]];
[self setDelegate:self.delegateContainer];
...it works! Is there a better way of retaining MapDelegate life cycle along with the one of MKMapView?
Thanks!
After waiting enough for any answers that could appear here and ensuring original problematic behavior twice more times, I am posting my own answer based on the second update from the question:
The problem with [self setDelegate:[[MapDelegate alloc] initWithMapView:self]]; string is that MapDelegate class should be able to be kept alive outside of the scope of question's initWithFrame method because delegate property has weak attribute. The possible solution is to create an instance variable serving as a container for a delegate class, for example:
#property (strong) id delegateClass;
....
[self setDelegateClass:[[MapDelegate alloc] init]];
[self setDelegate:self.delegateClass];
This solves the original problem.
LATER UPDATE
Though it is possible to set MKMapView's delegate in a separate class, I now realize that such model should not be used:
Currently I always prefer to use my controllers (i.e. controller layer in MVC in general) as delegates for all of my View layer classes (map view, scroll view, text fields): controller level is the place where all the delegates of different views can meet - all situated in controller layer, they can easily interact with each other and share their logic with the general logic of your controller.
On the other hand, if you setup your delegate in a separate class, you will need to take additional steps to connect your separate delegate with some controller, so it could interact with a rest part of your logic - this work have always led me to adding additional and messy pieces of code.
Shortly: do not use separate classes for delegates (at least view classes delegates provided by Apple), use some common places like controllers (fx for views like UIScrollView, MKMapView, UITableView or models like NSURLConnection).
I think viewDidLoad would be a better place to set up the map view. It's just a guess, but perhaps the crash is due to the view not being loaded yet.
Of course subclassing MKMapView isn't recommended at all. You would generally put your map as a subview, and set the main view to be the delegate. From the docs:
Although you should not subclass the MKMapView class itself, you can get information about the map view’s behavior by providing a delegate object.
Finally, if you really want to have a separate delegate class, you don't need to set its mapView, as all delegate methods pass the map as an argument.

How to add textfield data to cells

I have an array which I'm using to populate 2 cells with text for now. In a different view I have a textfield, a textview etc..
How can I move the data input by user in the textfield into an array in a different controller.
here's what I have:
- (void)viewDidLoad
{
tabledata = [[NSArray alloc] initWithObjects:#"Franklin", #"delossantos", nil];
[super viewDidLoad];
}
Here is the view controller of the textfields:
NewEntryController.m
#import "NewEntryViewController.h"
#implementation NewEntryViewController
#synthesize titleTextfield;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
#pragma mark - View lifecycle
- (void)viewDidUnload
{
[self setTitleTextfield:nil];
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
#end
I guess there are two options:
Separate your data in another NSObject class and make it a shared data model for both of your view controllers. Write to it when user inputs something.
(Really bad option) Keep everything as is, make sure the array in the first controller is publicly accessible and simply get the data from another controller.
Also, you declare an immutable array, you won't be able to change it. You will have to either recreate or declare it as mutable.
More on the first option
In order to create a shared model, you have to create a separate class.
Add some property to store data there, e.g.
#property (strong, nonatomic) NSMutableArray *userStrings;
Add #synthesize and initialise it in the init method of your new class.
Create an instance of it and assign it to some property in both view controllers, say, to:
#property (strong, nonatomic) NSObject *dataModel;
After you assign it, you will be able to access it from inside your view controllers. There, when your user inputs something, you will need to update contents of your dataModel by adding, changing or removing elements in the model's array. It will be done in some similar way:
[self.dataModel setUserStrings:[NSMutableArray arrayWithObjects: [self.textField text], [self.textView text], nil]];
// or
[[self.dataModel userStrings] addObject:[self.textField text]];
This way you can have a shared datasource.
Possible easier solution
It may be the case that you don't need the model to be shared simultaneously between two controllers, and maybe you open second one only after user has finished inputing and you create it from your first controller.
In that case you don't have to create and share the model, you can just create an array based on user input and pass it in your second controller. You would create that second controller and set it's property tableData. When your next controller appears it'd just read from that property and show the data.
If you need more info on such approaches, I'd suggest you watch some video tutorials on using MVC approach in Objective-C. For example, Stanford's course on iOS Programming is awesome. It is available for free in the iTunes U.

Problem with NSCollectionView

I'm quite a rookie when it comes to Cocoa programming, so I hope some experts can hint me on the right direction with a problem I'm facing.
I have a NSCollectionView that is bound to a NSArrayController (A) of elements having two properties: an URL to a picture and a NSArray (B) of elements of another class.
For every element inside the NSArrayController (A) , I load a subview with a NSImageView that should display the image in the URL and a NSTableView that is bound to the elements of the NSArray (B).
Everything works fine, except for one very important thing: the URL to the picture is not immediately available when I create the subview, it becomes available later, but when it does, I don't see the picture loading in the NSImageView.
I would think of a problem in the KVC/KVO implementation, but the strange thing is that the image loads correctly if, when the URL becomes available, the subview is not visible (e.g in a portion of the scrollview that is not displayed).
Example: The NSScrollview size is such that it can display only two subviews at a time. I add three elements to the NSArrayController (A): the first two images don't load, if I scroll down the scrollview to see the third element, I find the image loaded correctly.
Any ideas about what could cause such a strange behaviour?
Thank you in advance
Luca
series.h
#interface Series : NSObject {
#private
NSMutableString * banner;
}
-(Series*)initWithEpisode:(Episode*)ep;
-(void)setBanner:(NSString*)_banner;
#property (retain, readwrite) NSMutableString *banner;
#end
series.m
#implementation Series
#synthesize banner;
-(Series*)initWithEpisode:(Episode*)ep
{
self = [super init];
if(self){
banner = [[NSMutableString alloc]initWithString:#"some invalid URL"];
}
-(void) setBanner:(NSString*)_banner
{
[banner setString:[NSString stringWithFormat:#"some root path/%#", _banner];
}
-(void)dealloc
{
[super dealloc];
[banner release];
}
SeriesListViewController.m
-(void)VideoChangedStatus:(Episode*)ep{
//This is the delegate called by the object Episode when it retrieves the URL via asynchronous call to a webservice
Series *tmp = [[Series alloc]initWithEpisode:ep];
[[seriesList objectAtIndex:[seriesList indexOfObject:tmp]]setBanner:[ep banner]];
}
The binding is done in the subview nib file, to the NSImageView: I set File's Owner of type NSCollectionViewItem and then bind Valueurl to representedObject.banner
I didn't subclass NSCollectionView nor NSCollectionViewItem
After days of trying I found a solution that works: apparently it's not enough to use the setString method, I need to re-intialize the property inside the setBanner method
-(void) setBanner:(NSString*)_banner
{
banner = [NSMutableString[NSString stringWithFormat:#"some root path/%#", _banner]];
}
Still, I'd be very glad to know if someone has an explanation of why setString was causing that strange (to me) problem and why this solution works.
Thank you
Luca
I’m not sure why you’ve declared banner to be a mutable string — it looks like an immutable string would suffice.
At any rate, when you write a custom setter method you need to send -willChangeValueForKey: and -didChangeValueForKey: to ensure KVO (and hence bindings) compliance:
-(void) setBanner:(NSString*)_banner
{
[self willChangeValueForKey:#"banner"];
[banner setString:[NSString stringWithFormat:#"some root path/%#", _banner];
[self didChangeValueForKey:#"banner"];
}

Using the same NIB with multiple View Controllers

Basically I want to use a nib file and view controller as a template for a view that I plan to create a number of times. This nib will have a couple of labels and custom views.
The idea is that I will iterate through an array of objects and for each I will create an instance of this controller and set a property to that of the object from the array.
This all works nicely at the moment except for one thing - the labels won't update when I call setStringValue: !!!
I'm using a method within the view controller's code to make the change but it just doesn't work, I'm guessing that the IBOutlet isn't being hooked up properly, which is strange because the custom views are hooking up perfectly.
Any ideas?
Set a breakpoint on awakeFromNib and look in the debugger what the value of the label outlet is. All outlets should have been connected before awakeFromNib is being called. If it is still nil, you have your answer. Calling setStringValue: on nil does exactly "nothing". In that case you have not correctly bound the outlet or maybe you once had it bound correctly and later on changed the name, in that case there should be a yellow warning triangle in Xcode4 or interface builder indicating that something is wrong; however it will not prevent your app from building or running, the outlet will simply keep its initial value after object creation (which is nil).
When you alloc your NSViewControllers, just init with name of NIB:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
Thanks for the replies, they were helpful but not quite what I was getting at.
I ended up solving it by creating an empty NIB and filling it with just a custom NSView and a few other controls. I created an NSView subclass with IBOutlets for those controls and set the custom view's identity to my subclass in interface builder.
The trick in getting it to work each time I wanted to draw it was by making a class method in my subclass that would load the nib and return the view set up the way I wanted.
Code below:
+(id)todoViewFromNibWithFrame:(NSRect)frameRect todoList:(TodoList *)aTodoList
{
NSNib *todoViewNib = [[NSNib alloc] initWithNibNamed:#"TodoView" bundle:nil];
NSArray *objects = nil;
id todoView = nil;
[todoViewNib instantiateNibWithOwner:nil topLevelObjects:&objects];
for (id object in objects) {
if ([object isKindOfClass:[self class]]) {
todoView = object;
[todoView setTodoList:aTodoList];
break;
}
}
[todoViewNib release];
return todoView;
}
Thanks again for the replies!
Steve