i am a .net programmer and last week i started to read about objective-c. Class related stuff are kinda clear and today i learnt about protocols and delegates, i can't say it is 100% clear but i got it, it looks a lot with delegates and events from c#.
This is a simple example i created following a tutorial. It is all about 2 screens, the first one(a label and a button) launches the second one(a textbox and a button) which sends back a string. I think of it as a classic example of using events, no matter the programming language.
#import <UIKit/UIKit.h>
#import "ValueViewController.h"
#interface ViewController : UIViewController<ValueViewControllerDelegate>
- (IBAction)btnGetValue:(id)sender;
#property (weak, nonatomic) IBOutlet UILabel *lblCurrentValue;
#end
#import "ViewController.h"
#import "ValueViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)btnGetValue:(id)sender {
ValueViewController *valueVC = [self.storyboard instantiateViewControllerWithIdentifier:#"ValueViewController"];
valueVC.delegate=self;
[self presentViewController:valueVC animated:FALSE completion:nil];
}
-(void) sendValue:(ValueViewController *)controller didFihishWithValue:(NSString *)value
{
self.lblCurrentValue.text=value;
}
#end
#import <UIKit/UIKit.h>
#class ValueViewController;
#protocol ValueViewControllerDelegate<NSObject>
-(void) sendValue:(ValueViewController*) controller didFihishWithValue:(NSString*) value;
#end
#interface ValueViewController : UIViewController<UITextFieldDelegate>
#property (weak, nonatomic) IBOutlet UITextField *txtValue;
- (IBAction)btnSetValue:(id)sender;
#property (weak, nonatomic) id<ValueViewControllerDelegate> delegate;
#end
#import "ValueViewController.h"
#interface ValueViewController ()
#end
#implementation ValueViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.txtValue.delegate=self;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(BOOL)textFieldShouldReturn:(UITextField *)textField
{
return [textField resignFirstResponder];
}
- (IBAction)btnSetValue:(id)sender
{
[self.delegate sendValue:self didFihishWithValue:self.txtValue.text];
[self dismissViewControllerAnimated:FALSE completion:nil];
}
#end
My question is the following: Considering a, let's say, 30 screens application, which allows sending and receiving messages, adding friends , etc
Is it a good approach to group those 4-5 message view controller into a storyboard, those friends related view controllers into another storyboard and just make the connection like i did in that simple example, programmatically?
I saw that connections can be done in the designer without writing code, but sometimes i think you have to write code to send some arguments which means mixing the two(graphically and programmatically).
I just feel more comfortable, doing it programatically, maybe because this is how i do it in c#.
I am looking forward to you tips regarding organizing and making connections between screens.
PS: Sorry for writing such a long story(board) in here, i promise to make it shorter in my following posts.
Thanks.
Making two storyboards that communicate with each other would go against the intended flow, because storyboards were not intended for grouping parts of an application. Although an app may definitely have multiple storyboards, the intention behind allowing multiple storyboards was letting you support different screen paradigms (i.e. iPhone vs. iPad) or different localizations, not grouping related screens together.
Note, however, that storyboards are relatively new. You can define your views in NIB files, and use them instead. An unfortunate consequence of this choice is that you would need to make all your connections programmatically, but on the other hand you would be able to group your views inside you Xcode project using file groups or folders.
Related
Im new to programming with objective C and am working on moving data between View controllers. I am wondering if Bi-directional flow of data (variables) between ViewControllers is possible.
I can move data backwards (to the presentingViewController / sourceViewController) however i cannot move data forward (to the presentedViewController / destinationViewController).
I have made a simple case scenario (involving strings to get a principle of the idea) of this and it involves updating a UItextField on the destinationViewController using a UILabel in the sourceViewController and vice-versa.
I CANNOT update the UITextField using the UILabel, but can update the UILabel using the UITextField.
I have made Logs of different statements to track the variable values however when I switch ViewControllers the variables Data returns to null even if they are marked as strong.
Can you please offer any guidance, its been tearing away at my mind, or am I missing something obvious? I don't get why I keep getting a (null) value (in my NSLog) when I switch ViewControllers.
My sourceViewController / presentingViewController is named "ViewController."
My destinationViewController / presentedViewController is named "Gears2ViewController".
I have attached my code files below:
ViewController.h:
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
#property (strong, nonatomic) IBOutlet UILabel *outputLabel;
- (IBAction)ExitToHere:(UIStoryboardSegue *)sender;
#end
ViewController.m:
#import "ViewController.h"
#import "Gear2ViewController.h"
#interface ViewController ()
- (IBAction)changeItem:(id)sender;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)changeItem:(id)sender {
Gear2ViewController *G2VC=[[Gear2ViewController alloc] init];
G2VC.peterSido=[NSString stringWithFormat:#"%#",self.outputLabel.text];
[self performSegueWithIdentifier:#"toGear2" sender:self];
NSLog(#"ViewController UILabel reads %#",G2VC.peterSido);
}
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
}
- (IBAction)ExitToHere:(UIStoryboardSegue *)sender {
}
#end
Gears2ViewController.h:
#import <UIKit/UIKit.h>
#import "ViewController.h"
#interface Gear2ViewController : UIViewController
#property (strong, nonatomic) NSString *peterSido;
#end
Gears2ViewController.m:
#interface Gear2ViewController ()
#property (strong, nonatomic) IBOutlet UITextField *updatedOutput;
- (IBAction)updateOutput:(id)sender;
#end
#implementation Gear2ViewController
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(#"Gears2ViewController ViewDidAppear reads %#",self.peterSido);
}
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"Gears2ViewController ViewDidLoad responds %#",self.peterSido);
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)updateOutput:(id)sender {
self.peterSido = self.updatedOutput.text;
((ViewController *)self.presentingViewController).outputLabel.text = self.peterSido;
NSLog(#"Gears2View Controller updating ViewController UILabel reads %#",self.peterSido);
}
#end
NSLog:
2015-06-29 18:52:58.798 testerBeta[21735:645772] Gears2ViewController ViewDidLoad responds (null)
2015-06-29 18:52:58.799 testerBeta[21735:645772] ViewController UILabel reads I like Pie
2015-06-29 18:52:59.317 testerBeta[21735:645772] Gears2ViewController ViewDidAppear reads (null)
2015-06-29 18:53:12.651 testerBeta[21735:645772] Gears2View Controller updating ViewController UILabel reads No I dont
Quite Lengthy but Thanks in Advance!!!
You want to pass the data in prepareForSegue:, like so:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)__unused sender
{
if ([[segue identifier] isEqualToString:#"toGear2"])
{
Gear2ViewController *controller = (Gear2ViewController *)segue.destinationViewController;
controller.peterSido = self.outputLabel.text;
}
}
The reason why is that the segue instantiates the presented view controller for you, and you then set the property of the instantiated view controller which the segue will present.
To pass the data back, you can use an unwind segue, which can get the value from the presented view controller's property.
- (IBAction)unwindFromGear2:(UIStoryboardSegue *)segue
{
Gear2ViewController *controller = (Gear2ViewController *)segue.sourceViewController;
self.outputLabel.text = controller.peterSido;
}
This is the proper way to pass data back and forth via segues. Gear2ViewController shouldn't be setting properties on its presentingViewController.
Update:
The preferred way to test that a property isn't nil is like this:
if (self.peterSido)
{
self.updatedOutput.text = self.peterSido;
}
else // No need for if test here
{
self.updatedOutput.text = #"";
}
That's the long form, but the assignment and if test can be more concisely written as:
self.updatedOutput.text = self.peterSido ?: #"";
When you declare any variable as #property then you need to synthesize it in .m file .
You have declared your outputLabel as #property but you missed to synthesize it in .m file.
When you synthesize any variable then it allows you to get and set the values to it .
Do it it will help you.
Thank you.
This is a follow-up on the previous question.
Sorry. I could not figure out how to add code or edit something written over 5 minues ago.
A brief summary. I am trying to display a customized/derived TableView over a regular View. I am not using IB, but doing everything from the code. The goal here is to build the application, but also to learn Cocoa/OSX programming. This is my first OSX coding attempt.
NSView atop of which I would like to display my custom TableView is being displayed fine. Please excuse the NSLog garbage. It helps me to learn about the app lifecycle.
Header:
#import <Cocoa/Cocoa.h>
#import "MSNavigationTableView.h"
#interface MSNavigationPanelView : NSView
#property (strong, nonatomic) IBOutlet MSNavigationTableView *myNavigationTable;
#end
code:
#import "MSNavigationPanelView.h"
#implementation MSNavigationPanelView
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
NSLog(#"Initializing Navigation Panel");
}
self.myNavigationTable = [[MSNavigationTableView alloc] initWithFrame:self.frame];
[self.myNavigationTable setDataSource:self.myNavigationTable];
[self.myNavigationTable setDelegate:self.myNavigationTable];
[self addSubview:self.myNavigationTable];
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
[super drawRect:dirtyRect];
// Drawing code here.
NSLog(#"Drawing navigation view!");
}
#end
Now the NSTableView derived class.
Header:
#import <Cocoa/Cocoa.h>
#interface MSNavigationTableView : NSTableView <NSTableViewDataSource>
#end
NSArray *myNavigationArray;
Source:
#import "MSNavigationTableView.h"
#implementation MSNavigationTableView
+ (void)initialize {
NSLog(#"Called NavigationTableView::initialize!");
myNavigationArray = [NSArray arrayWithObjects:#"Call History" #"Contacts", #"Messages", #"Voicemail", nil];
}
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
[super drawRect:dirtyRect];
// Drawing code here.
}
- (NSInteger)numberOfRowsInTableView: (NSTableView *) aTableView
{
return [myNavigationArray count];
}
- (id)tableView: (NSTableView*) aTableView objectValueForTableColumn: (NSTableColumn *)aTableColum row: (NSInteger)rowIndex
{
NSLog([myNavigationArray objectAtIndex:rowIndex]);
return [myNavigationArray objectAtIndex:rowIndex];
}
#end
Thank you. I am sure that I am doing something stupid, and/or perhaps not doing something necessary. I have tried to figure this out for a couple of hours. No ideas so far.
You really need to use Interface Builder to make a table.
I would never try to programmatically initialized a table... to many things to configure.
NSTableView needs to have NSTableColumns, NSTableColumns need to have NSCell's, etc. etc.
NSTableView needs to be embedded in an NSScrollView.
I figured out what needs to be done.
First, array initialization has to be moved from +(void)initialize to another method. For me - (id)initWithFrame works fine.
Second, while this was not clear for me, overwriting NSTableViewDataSource is not enough.
One has to create NSTableColumn(s) then add the column(s) to the table using addTableColumn method of NSTableView class. Once that is done, we proceed with setDataSource and so on.
I'm using the Cordova cleaver to insert some subview into parts of my native app. I'm having difficulty retaining the contents of these subviews between the pages of my app. For example if I go from ViewController1 to ViewController2 and then back again the contents of the subview on the first view controller has reset as if it had just been loaded for the first time. I'd like a way to preserve these subviews across the app so they don't reset as a user moves around.
Here's what I'm doing right now:
Retaining the subview as a property in ViewController.h
#import <UIKit/UIKit.h>
#import <Cordova/CDVViewController.h>
#interface ViewController : UIViewController
#property (nonatomic,retain) CDVViewController* viewController;
#end
And then loading it here like so in ViewController.m
#import "ViewController.h"
#import <Cordova/CDVViewController.h>
#interface ViewController ()
#end
#implementation ViewController
#synthesize viewController;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
viewController = [CDVViewController new];
viewController.useSplashScreen = NO;
viewController.view.frame = CGRectMake(0, 44, 320, 450);
[self.view addSubview:viewController.view];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
Any help or pointing in the right direction is greatly appreciated.
There are a few approaches you could take here. One of them is to use a singleton pattern for your view controller, so that only one instance is ever created. With that pattern, the view controller will retain its state because it will never be re-created. An example of that pattern for objective c is here.
But that may not be the best approach. Another option is to store the parts of your view controller that you want to keep the same as static variables, so that if a new instance of the view controller is created, the portions of your view controller that you want to preserve will still be the same. If you do that, you can restore the state of your view controller in a viewDidAppear method.
If you were to use the second approach, I would do it like this. First, remove the #property declaration from your header file for the CDVViewController. Then, in your implementation file do something like this:
#import "ViewController.h"
#import <Cordova/CDVViewController.h>
#interface ViewController ()
#end
#implementation ViewController
static CDVViewController *__MY_STATIC_CDVViewController;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
if( ! __MY_STATIC_CDVViewController ) {
__MY_STATIC_CDVViewController = [CDVViewController new];
__MY_STATIC_CDVViewController.useSplashScreen = NO;
__MY_STATIC_CDVViewController.view.frame = CGRectMake(0, 44, 320, 450);
}
[self.view addSubview: __MY_STATIC_CDVViewController.view];
}
#end
I would add that this recommendation isn't the best overall approach for your problem, but it should work for your needs.
I have a very simple project. Extremely watered down. All it does is load some text into an NSTableView. That's it. But it's using a new window and controller, called "Revisions."
As soon as the new window becomes active, it crashes or just locks up. No errors in the console. If it sits in the background, behind the AppDelegate's window, it appears to load the information fine. I can see the table is populated perfectly. But as soon as I click on the window and make it active, it crashes/locks.
This is driving me nuts. I know it has to do with memory management, but I can't figure out where or how or why.
Note, I'm in XCode 4.2, where there's no more releasin' (unless I change some settings, of course).
All connections in
AppDelegate.h
#import <Cocoa/Cocoa.h>
#interface AppDelegate : NSObject <NSApplicationDelegate>
#property (assign) IBOutlet NSWindow *window;
#end
AppDelegate.m
#import "AppDelegate.h"
#import "Revisions.h"
#implementation AppDelegate
#synthesize window = _window;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
Revisions *rev = [[Revisions alloc] initWithWindowNibName:#"Revisions"];
[rev loadWindow];
}
Revisions.h
#import <Cocoa/Cocoa.h>
#interface Revisions : NSWindowController
{
IBOutlet NSTableView *quicktimesList;
IBOutlet NSTableView *unusedDataList;
}
#end
Revisions.m
#import "Revisions.h"
#implementation Revisions
- (id)initWithWindow:(NSWindow *)window
{
self = [super initWithWindow:window];
if (self) {
// Initialization code here.
}
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.
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
{
NSLog(#"Creating number of rows.");
return 10;
}
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
NSLog(#"Starting Loop.");
NSString *words = [[NSString alloc] initWithFormat:#"Row %i", rowIndex];
NSLog(#"Looping %i", (int)rowIndex);
return words;
}
#end
Ok. I'm going to give you a couple of tips when dealing with potential memory leaks in Xcode 4.2.
When writing software for Mac it is advisable to enable garbage collection in your build settings. Just simply search for "garbage collection" in the search bar of your build settings and set it to "required".
If you have memory leaks in your project just press the "product" menu and hit "Analyze".This does as the menu item states, it analyses your project for potential memory leaks and helps you track them down.
Hope this helps!
I started my intrepid journey into learning objective-c for ios, and got as far as trying to build my view in the interface builder, when I realized that I can't link up the buttons I'm creating to my File's Owner. I have made sure that my File's owner has my view controller selected, and have tried restarting xcode and the interface builder. Here's the contents of both my .h and .m files:
My CalculatorViewController.h file:
#import <UIKit/UIKit.h>
#import "CalculatorBrain.h"
#interface CalculatorViewController : UIViewController {
IBOutlet UILabel *display;
CalculatorBrain *brain;
}
- (IBAction):digitPressed:(UIButton *)sender;
- (IBAction):operationPressed:(UIButton *)sender;
#end
And the CalculatorViewController.m file:
#import "CalculatorViewController.h"
#implementation CalculatorViewController
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[super dealloc];
}
#end
In short, every time I click on a button in my Interface Builder View, hold CTRL, and drag the blue line over to "File's Owner," nothing happens. In the tutorial I'm watching (The Stanford Fall 2010 IOS tutorials, lesson 2 - if that helps) shows File's Owner highlighting and working like a champ. Any help would be much appreciated. Thanks!
Invalid definition of IBActions. (Extra colon)
Change
- (IBAction):digitPressed:(UIButton *)sender;
- (IBAction):operationPressed:(UIButton *)sender;
To
- (IBAction) digitPressed:(UIButton *)sender;
- (IBAction) operationPressed:(UIButton *)sender;
So you want to link up your button with an IBAction? Did I get this correct? You need to right click on the button, select the event (usually Touch Up Inside) and then drag (from the circle to the right of the event) to the Owner. Now if you want to hook something up to an IBOutlet (usually you do this with UITextField etc.) you will drag the File Owner over to the control and select the outlet from the popup.
#import "CalculatorViewController.h"
#implementation CalculatorViewController
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (IBAction):digitPressed:(UIButton *)sender{
}
- (IBAction):operationPressed:(UIButton *)sender{
}
- (void)dealloc {
[super dealloc];
}
#end
There should be no colon after - (IBAction)
2 things:
There can't be any colon after - (IBAction)
Add this line to your code.
//.h file
IBOutlet UIButton *yourButtonName;
#property (nonatomic, retain)IBOutlet UIButton *yourButtonName;
/.m file
#synthesize yourButtonName;
Now drag it and you will done.