How to generate a generic table view controller? - objective-c

I've created a custom TablePickerViewController which is a subclass of UITableViewController. I'm using this class to display a list of object of a custom type TablePickerItem.
I'm using TablePickerViewController multiple times in my iOS application to show different kinds of lists where the user has to pick an item -- and then another view controller MainViewController should react on this selection and do something.
I've created this protocol and created a delegate property in the TablePickerViewController:
#protocol TablePickerViewControllerDelegate <NSObject>
- (void)tablePickerViewController:(TablePickerViewController *)controller
didSelectItem:(TablePickerItem*)item;
#end
When I setup a new TablePickerViewController in MainViewController it is also set as delegate -- than it will be notified when the user taps an cell in the table view.
The problem is that my MainViewController will setup multiple TablePickerViewController with different data (TablePickerItem). How should I setup my MainViewController to handle these multiple TablePickerViewController? Events from each of them will results in calling to the same protocol-method in my MainViewController.
Further I need to get the element which the TablePickerItem represents, as I need to know for instance the elements ID when acting in the tablePickerViewController:didSelectItem method. Should I just handle this by adding something like #property (nonatomic) id element to the TablePickerItem and set the original object into this property then creating it?
Maybe someone can give me an example on how to create an generic table view controller, if my solutions seems being done in the wrong way.

I'm not entirely sure of your set up, but if you have multiple pickers that feedback to the main controller then you could just have a reference to the picker e.g.
// MainViewController.m
#interface MainViewController ()
#property (nonatomic, strong) TablePickerViewController *picker1;
#property (nonatomic, strong) TablePickerViewController *picker2;
// ... and so on. Obviously you know your problem domain so you can change
// the terrible naming above to something appropriate
#end
#implementation MainViewController
// ...
- (void)theMethodWhereYouSetUpYourPickers;
{
TablePickerViewController *picker1 = [[TablePickerViewController alloc] init];
picker1.delegate = self;
self.picker1 = picker1;
// ...
}
- (void)tablePickerViewController:(TablePickerViewController *)controller
didSelectItem:(TablePickerItem*)item;
{
if (controller == self.picker1) {
NSLog(#"Something was picked in picker 1 %#", item);
} else if (controller == self.picker2) {
NSLog(#"Something was picked in picker 2 %#", item);
}
}
// ...
#end

Related

Passing Data from view Controller to NSObject class

I know how to pass the data from one view controller to another view controller ,now i want to know how to pass a textfield value from view controller to NSObject class and how to store the recieved in nstring .Please help me to do this , Can anyone give a example for this ,
I think what you're asking is how to store data in a model object for use by your view controller. If this is not your meaning, then please forgive me.
You are right that a model object should inherit from NSObject. Optionally, you could also extend another model object to add property values. Model objects are a great way to separate the view objects from your data.
Let's say you have a CustomerViewController with some customer fields. You need to populate those customer fields, and potentially perform some processing on that data. The model object supports these relationships, by allowing you to separate your views from any processing logic related to your data and business rules.
Using the relationships below as a guide, you should be on your way to building effective view controllers, that separate your views from your data!
CustomModel Interface
#interface CustomerModel : NSObject
#property (strong, nonatomic) NSString *firstName, *lastName;
#property (strong, nonatomic) NSString *phoneNumber;
- (BOOL) isValidPhoneNumber:(NSString *)phoneNumber;
#end
CustomerModel Implementation
#import "CustomerModel.h"
#implementation CustomerModel
- (BOOL) isValidPhoneNumber:(NSString *)phoneNumber
{
//Check that phone number can be parsed and is valid
}
#end
CustomerViewController Implementation
#import "CustomerViewController.h"
#import "CustomerModel.h"
#interface CustomerViewController () <UITextFieldDelegate>
#property (strong, nonatomic) CustomerModel *customerModel;
...
#property (weak, nonatomic) IBOutlet UITextField *firstNameField
...
#end
#implementation CustomerViewController
- (void) viewDidLoad {
//Optionally instantiate the customer model with stored data,
// to pre-populate the view controller.
self.customerModel = [CustomerModel new];
self.firstNameField.delegate = self;
}
- (void) textFieldDidEndEditing:(UITextField *)textField {
//Validate the phone number
NSString *phoneNumber = textField.text;
if ([self.customerModel isValidPhoneNumber:phoneNumber]) {
self.customerModel.phoneNumber = phoneNumber;
} else {
//Alert the user that the data is invalid
}
}
- (BOOL) textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
return YES;
}
#end
Possible improvements
It might be a little annoying to the User, to see alerts while filling out information. So, it might be better to defer the validation to when a save button is pressed.
Model objects can be populated from a data store, to be used to pre-populate a form.

How to initialize main window objects from custom class during program start?

I have a main window with a couple of popupbuttons. I want to clear them, then load the lists from a method in a custom class. I've got my view controller working and I know the method in the custom class (newRequest) is working because I added a NSLog command to print "Test" when the method executes. In AppDelegate I'm calling the method via:
[polyAppRequest newRequest];.
As I said, I know the method is executing. Why can't I removeallitems from the popupbutton from this custom class method?
Thanks
Keith
I read that you should use an NSWindowController to manage a window. See here:
Windows and window controllers
Adding views or windows to MainWindow
Then if your window gets complicated enough, the NSWindowController can employ various NSViewControllers to manage parts of the window.
In any case, I used an NSWindowController in my answer.
The image below shows the outlet's for File's Owner, which is my MainWindowController:
I created MainWindowController .h/.m in Xcode6.2 by:
Selecting File>New>File>OS X - Source - Cocoa Class
Selecting NSWindowController for Subclass of:
Checking also create .xib file for user interface
Then I deleted the window--not the menu--in the default MainMenu.xib, and I changed the name of MainWindowController.xib, created by the steps above, to MainWindow.xib.
The following code works for me (but I'm a Cocoa beginner!):
//
// AppDelegate.m
// PopUpButtons
#import "AppDelegate.h"
#import "MainWindowController.h"
#interface AppDelegate ()
#property(strong) MainWindowController* mainWindowCtrl;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
[self setMainWindowCtrl:[[MainWindowController alloc] init]];
[[self mainWindowCtrl] showWindow:nil];
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
#end
...
//
// MainWindowController.m
// PopUpButtons
//
#import "MainWindowController.h"
#import "MyData.h"
#interface MainWindowController ()
#property(strong) MyData* data;
#property(weak) IBOutlet NSPopUpButton* namePopUp;
#property(weak) IBOutlet NSPopUpButton* agePopUp;
#end
#implementation MainWindowController
-(id)init {
if (self = [super initWithWindowNibName:#"MainWindow"]) {
_data = [[MyData alloc] init]; //Get data for popups
}
return self;
}
- (void)windowDidLoad {
[super windowDidLoad];
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
[[self namePopUp] removeAllItems];
[[self namePopUp] addItemsWithTitles:[[self data] drinks]];
[[self agePopUp] removeAllItems];
[[self agePopUp] addItemsWithTitles:[[self data] extras]];
}
#end
...
//
// MyData.h
// PopUpButtons
//
#import <Foundation/Foundation.h>
#interface MyData : NSObject
#property NSArray* drinks;
#property NSArray* extras;
#end
...
//
// MyData.m
// PopUpButtons
//
#import "MyData.h"
#implementation MyData
- (id)init {
if (self = [super init]) {
_drinks = #[#"coffee", #"tea"];
_extras = #[#"milk", #"sugar", #"honey"];
}
return self;
}
#end
I hope that helps. If you need any more screenshots, let me know.
Edit1:
I think I see what you are asking about. Although I don't think it is a very good approach, if I change my code to this:
//
// MyData.h
// PopUpButtons
//
#import <Cocoa/Cocoa.h>
#interface MyData : NSObject
#property (copy) NSArray* drinks;
#property (copy) NSArray* extras;
-(void)newRequest;
#end
...
//
// MyData.m
// PopUpButtons
//
#import "MyData.h"
#interface MyData()
#property (weak) IBOutlet NSPopUpButton* drinksPopUp;
#property (weak) IBOutlet NSPopUpButton* extrasPopUp;
#end
#implementation MyData
- (id)init {
if (self = [super init]) {
_drinks = #[#"coffee", #"tea"];
_extras = #[#"milk", #"sugar", #"honey"];
}
return self;
}
-(void)newRequest {
[[self drinksPopUp] removeAllItems];
[[self drinksPopUp] addItemsWithTitles:[self drinks]];
[[self extrasPopUp] removeAllItems];
[[self extrasPopUp] addItemsWithTitles:[self extras]];
}
#end
I am unable to populate the NSPopUpButtons. This is what I did:
I dragged an Object from the Object Library to the dock in IB, and in the Identity Inspector, I changed the Object's class to MyData.
Then I clicked on the Connections Inspector, and the two instance variables in MyData, drinksPopUp and extrasPopUp, were listed in the Outlets.
I dragged from the outlets to the respective NSPopUpButtons.
I guess I assumed, like you, that when my program ran, the NSPopUpButtons would be assigned to the instance variables drinksPopUp and extrasPopUp--but that doesn't seem to be the case. According to the Apple docs, you should be able to do that:
An application typically sets outlet connections between its custom
controller objects and objects on the user interface, but they can be
made between any objects that can be represented as instances in
Interface Builder,...
Edit2:
I am able to pass the NSPopUpButtons from my MainWindowController to the newRequest method, and I can use the NSPopUpButtons inside newRequest to successfully populate the data.
Edit3:
I know the method in the custom class (newRequest) is working because
I added a NSLog command to print "Test" when the method executes.
But what happens when you log the variables that point to the NSPopUpButtons? With my code in Edit1, I get NULL for the variables, which means the NSPopUpButtons never got assigned to the variables.
Edit4:
If I add an awakeFromNib method to MyData, and inside awakeFromNib I log the NSPopUpButton variables for the code in Edit1, I get non NULL values. That tells me that the MainWindowController's windowDidLoad method is executing before MyData's awakeFromNib method, and therefore you cannot call newRequest inside MainWindowController's windowDidLoad method because MyData has not been fully initialized.
Edit5:
Okay, I got the code in Edit1 to work. The Apple docs say this:
About the Top-Level Objects
When your program loads a nib file, Cocoa recreates the entire graph
of objects you created in Xcode. This object graph includes all of the
windows, views, controls, cells, menus, and custom objects found in
the nib file. The top-level objects are the subset of these objects
that do not have a parent object [in IB]. The top-level objects typically
include only the windows, menubars, and custom controller objects that
you add to the nib file [like the MyData Object]. (Objects such as File’s Owner, First
Responder, and Application are placeholder objects and not considered
top-level objects.)
Typically, you use outlets in the File’s Owner object to store
references to the top-level objects of a nib file. If you do not use
outlets, however, you can retrieve the top-level objects from the
nib-loading routines directly. You should always keep a pointer to
these objects somewhere because your application is responsible for
releasing them when it is done using them. For more information about
the nib object behavior at load time, see Managing the Lifetimes of
Objects from Nib Files.
In accordance with the bolded line above, I changed this declaration in MainWindowController.m:
#interface MainWindowController ()
#property(strong) MyData* data;
...
#end
to this:
#interface MainWindowController ()
#property(strong) IBOutlet MyData* data;
...
#end
Then, in IB I dragged a connection from the MainWindowController data outlet to the MyData Object(the Object I had previously dragged out of the Object Library and onto the doc).
I guess that causes MyData to unarchive from the .xib file and initialize before MainWindowController.

Basic MVC: set variable to NSTextField input in different classes

The main idea:
Model: Set the value from the textfield to a variable which I can call/log.
View: Just a NSTextField hooked up to the Model class.
Controller: NSButton hooked up to the ViewController.
As you will notice, it logs the basic string from NSLog, also the predefined beginvalue. but when I ask for the txtBegin value it returns NULL
I know the TextField and the Button are hooked up in the connections inspector.
Screenshot:
Downloadable project:
ViewController.h
#import <Cocoa/Cocoa.h>
#import "Model.h"
#interface ViewController : NSView
- (IBAction)logTheVariable:(id)sender;
#end
ViewController.m
- (IBAction)logTheVariable:(id)sender
{
Model *myModel = [[Model alloc]init];
[myModel doSomething];
}
Model.h
#import <Foundation/Foundation.h>
#interface Model : NSObject{
//iVars
int begin;
}
//properties
#property (weak) IBOutlet NSTextField *txtBegin;
//methods
-(void)doSomething;
#end
Model.m
#import "Model.h"
#implementation Model
-(void)doSomething{
NSLog(#"I'm in the Model Class"); //logs like a charm!
begin = 5; //just a test to see if it logs anything (which works)
NSLog(#"%d",begin);// logs like a charm!
//->Problem is here <-
NSLog(#"%#",_txtBegin.stringValue); //returns a "NULL" value.
//->Problem is here <-
}
#end
Simple solution just declare the outlet of textfield in your viewcontroller and then modify below method in your model class and implement it:-
Model.h
-(void)doSomething: (NSString*)yourstringvalue;
Model.m
-(void)doSomething: (NSString*)yourstringvalue
{
NSLog(#"%#",yourstringvalue);
}
Viecontroller.m
- (IBAction)logTheVariable:(id)sender
{
Model *myModel = [[Model alloc]init];
NSString * str=self.begintext.stringValue;
[myModel doSomething:str];
}
The Model class instance you use in logTheVariable: is logging a null value because it is a new instance you created in the action of the ViewController, not an instance of Model interface builder is aware of.
- (IBAction)logTheVariable:(id)sender
{
Model *myModel = [[Model alloc]init];
//This is a new instance. The IBOutlet for txtBegin is null.
[myModel doSomething];
}
What you have implemented is not how MVC is intended. Apple provides a thorough roadmap of the user interface, framework and programming concepts you need to know to develop for OSX that will help you understand how Apple expects you to use their frameworks. https://developer.apple.com/library/mac/referencelibrary/GettingStarted/RoadMapOSX/chapters/01_Introduction.html
Models generally don't know anything about the user interface. They just store data and communicate when data is changed.
Your Model class should expose a property for any data it wants to expose.
Your Model class should not have any reference to the NSTextField.
So now in your model, you can log when your property is changed
-(void)doSomething:(NSString *)value //method name should be setBegin assuming you name your property 'begin'
{
NSLog(#"I'm in the Model Class"); //logs like a charm!
begin = 5; //just a test to see if it logs anything (which works)
NSLog(#"%d",begin);// logs like a charm!
//->Problem is here <-
NSLog(#"%#",value); //will log like a charm
}
Views generally doesn't know anything about the model. It just displays data in a way the user can interact with and possibly edit.
Controllers tie the Model and View together. It receives notification from the model when data changes and updates the View. Conversely, it also receives notification from the View when the data is edited to update the Model.
Your ViewController class should have a reference to the NSTextField (using an outlet)
Your ViewController class should have an instance of Model which it creates internally.
Now your logTheVariable can be implemented to tie the Model and View together:
- (IBAction)logTheVariable:(id)sender
{
//Use ViewController's model instance
Model *myModel = [self myModel];
NSString * value = [[self txtBegin] stringValue];
[myModel doSomething:value];
}

Objective-c: Singleton - passing variables

I have a singleton that I'd like to use to manage the onscreen animation of my views. Here's my.
#import <Foundation/Foundation.h>
#interface OAI_AnimationManager : NSObject {
NSMutableDictionary* sectionData;
}
#property (nonatomic, retain) NSMutableDictionary* sectionData;
+(OAI_AnimationManager* )sharedAnimationManager;
- (void) checkToggleStatus : (UIView* ) thisSection;
#end
.m file
#import "OAI_AnimationManager.h"
#implementation OAI_AnimationManager
#synthesize sectionData;
+(OAI_AnimationManager *)sharedAnimationManager {
static OAI_AnimationManager* sharedAnimationManager;
#synchronized(self) {
if (!sharedAnimationManager)
sharedAnimationManager = [[OAI_AnimationManager alloc] init];
return sharedAnimationManager;
}
}
- (void) checkToggleStatus : (UIView* ) thisSection {
//get the section data dictionary
NSLog(#"%#", sectionData);
}
#end
You'll see in the .h file I added a NSMutableDictionary and am using #property/#synthesize for it's getter and setter.
In my ViewController I instantiate the animation manager as well as a series of subclasses of UIView called Section. With each one I store the data (x/y w/h, title, etc.) in a dictionary and pass that to the dictionary delcared in animation manager. In the Section class I also instantiate animation manager and add a UITapGestureRecognizer which calls a method, which passes along which section was tapped to a method (checkToggleStatus) in animation manager.
As you can I see in the method I am just logging sectionData. Problem is I am getting null for the value.
Maybe my understanding of singletons is wrong. My assumption was the class would only be instantiated once, if it was already instantiated then that existing object would be returned.
I do need all the other Section classes data as if one animates others animate in response and I can get around it by passing the tapped Section to the animation manager and doing [[Section superview] subviews] and then looping and getting the data from each that way but it seems redundant since that data is available in the ViewController when they are created.
Am I doing something wrong in trying to transfer that data? Is there a better solution? I am open to suggestions and criticisms.
Thanks
h file
#interface OAI_AnimationManager : NSObject
#property (nonatomic, retain) NSMutableDictionary* sectionData;
+(OAI_AnimationManager* )sharedAnimationManager;
- (void) checkToggleStatus : (UIView* ) thisSection;
#end
m file
static OAI_AnimationManager* _sharedAnimationManager;
#implementation OAI_AnimationManager
#synthesize sectionData = _sectionData;
+(OAI_AnimationManager *)sharedAnimationManager {
#synchronized(self) {
if (!_sharedAnimationManager) {
_sharedAnimationManager = [[OAI_AnimationManager alloc] init];
}
}
return _sharedAnimationManager;
}
- (void) checkToggleStatus : (UIView* ) thisSection {
//get the section data dictionary
NSLog(#"%#", _sectionData);
}
#end
Notice I moved your sectionData variable from the header and moved it to the implementation file. A while back, they changed it to where you can synthesize properties and specify their instance variable names along side it... hence:
sectionData = _sectionData;
I also added and underscore to the instance variable... this is a universal convention for private variables and it also will throw a compile error now if you try to type just sectionData as you did in the return statement of checkToggleStatus:. Now you either have to type self.sectionData or _sectionData.
You didn't include the code that creates an instance of your dictionary but I bet you didn't set it as self.sectionData = [[NSDictionary alloc] init] which means it would not retain the value and you would get null the next time you called it. Classic memory management mistake... I know it well because I learned the hard way hehehe

share NSArray between different UIViewControllers

I am making an application that uses a webService to get data in a JSON format... I get the data I parse them into a object NSArray ... and i use it .. it works fine ...
Now, if the user clicks a button I need to send him to an other Uiview ... which contains more data about the clicked object ..
The problem is here ... I don't want to request again and download the result from the server ... because i already did ... All I want is to have access to that NSArray that I have in the first UIViewController.
You can add on AnotherView.h another property:
#property (nonatomic, retain) NSArray *jsonData;
On AnotherView.m synthesize it. When you are going to to call AnotherView from InitialView, you can set jsonData with the data you retrieved on InitialView.
Create a custom initializer in your other view controller like so:
#import <UIKit/UIKit.h>
#interface OtherViewController : UIViewController
#property (nonatomic, strong) NSArray *myArray;
- (id)initWithArray:(NSArray *)anArray;
#end
Then implement it like so:
#import "OtherViewController.h"
#implementation OtherViewController
#synthesize myArray=_myArray;
- (id)initWithArray:(NSArray *)anArray {
if (!(self = [self initWithNibName:#"OtherViewController" bundle:nil]))
return nil;
if (!anArray) {
#throw [NSException exceptionWithName:#"OtherViewControllerBadInitCall" reason:#"array is nil" userInfo:nil];
}
_myArray = anArray;
return self;
}
//...
#end
You can then init and display your controller like so:
OtherViewController *otherViewController = [[OtherViewController alloc] initWithArray:greatJSONArray];
[self.navigationController pushViewController:otherViewController animated:YES];
There you go.
You can set the array as the property. You can either create a new class and set the array as the property and after you fetch the array, set the property. Or, you can create a property of the existing UIVIewController Class and pass the object.
Either way, you have to set property.
You could define a new property in your second ViewController that holds an NSArray and pass the firt array to the second ViewController before show it.
Well you have not outlined whether you send the data forward or backward. In the later case you will need to implement protocol and delegate(Define your own protocol) but for the prior case you just need to create the property of the Object you want to access in any other class. In case of web-services it is better to use protocol and delegates if u abide by the norms of MVC architecture.