Modifying properties of a view controller form another view controller - objective-c

In my project, there are two view controllers - let's say firstViewController and secondViewController. The second view controller has a button, and I want to make sure when the button gets pressed, the second view controller is telling somehow the first view controller - "hey, I got pressed, do something!", and it will do something, like changing a label. How is this possible to perform? Thanks in advance. Some code :
#interface firstViewController : UIViewController
#property (weak, nonatomic) IBOutlet UILabel *textLabel;
#end
#implementation firstViewController
#synthesize textLabel;
#end
#interface secondViewController : UIViewController
-(IBAction)buttonPressed;
#end
#implementation secondViewController : UIViewController
-(IBAction)buttonPressed{
// Hey, I got pressed! Set the text on textLabel to "OK"!
}
#end

This is a very simple case of delegation and protocol mechanism of objective-c..
have a look at this tutorial which will explain you how its done.. you can do this via notification also but that is not usually advised...(because notification is usually used when the receiver is unknown , like in the case of UIDeviceBatteryLevelDidChangeNotification you don't exactly know which view controller wants to know about this.)

I'd first consider what the button press means. Does it change the state of the model?
Say your model is an int, and the button increments it. The view controllers wouldn't message each other about that, they would just both observe the state of the model. (The one with the button could change the state, too).
Thinking about it this way, the solution probably isn't delegation. It's probably notification or KVO.

See the answer to this question: Passing data between two view controllers via a protocol
However, ask yourself if you really need a protocol here. If it is just between this classes or just about the question of accessing data of a class or sending information to a class then that is what the interface of a class is made for.
#interface firstViewController : UIViewController{
UILabel *textLabel; // I personally alway add IBOutlet here too, but I think that is not required.
}
#property (weak, nonatomic) IBOutlet UILabel *textLabel;
#end
And in SecondViewController.m:
#import "FirstViewController.h"
#implementation secondViewController : UIViewController
-(IBAction)buttonPressed{
// You will have to have a properly set instance variable firstViewController
[firstViewController.textLabel setText:#"OK"];
}
#end
So your second view controller needs to 'know' the first one. One way of achieving that is defining
FirstViewController *firstViewController;
as property and set it from wherever the second view controller is created and the first one is already known. How to do that exactly depends very much on the architecture of your app.

Related

Simple passing of data through delegation in objective C

I'm using Xcode to write an app in objective c. I am trying to pass data from a container view controller to the parent view controller using delegation. I have successfully passed the data to the parent view controller, but all of the documentation sets what I have sent to the .h header file in the .m implementation file using viewDidLoad or viewDidAppear. I was wondering, since the view is already present, if there is a way to detect that data has been changed in a view and automatically run a method or code to update the view with the new information. Something along the idea of didReceiveNewData or didEditExistingValues (of course those arent real methods). Thank you for your help!
Edit: What I have done so far:
I want to pass the data from MainFeedTableViewController to MainFeedViewController (The first is in a container inside of the second). I want to set the title of the custom navigation bar in MainFeedViewController to something described in the MainFeedTableViewController.
In the MainFeedTableViewController.m (the view sending data) I have:
#import "MainFeedTableViewController.h"
#import "FeedViewController.h"
#interface MainFeedTableViewController ()
#end
#implementation MainFeedTableViewController
- (IBAction)swipeLeftDetected:(UIGestureRecognizer *)sender {
UIStoryboard *mc = self.storyboard;
FeedViewController *fv = [mc instantiateViewControllerWithIdentifier:#"FeedViewController"];
fv.navigationBarTitleToSet = #"HOPING TO SET TITLE TO THIS";
[self performSegueWithIdentifier:#"MainToLocalFeed" sender:self];
}
and some other unrelated stuff..
In the MainFeedTableViewController.h I have:
#import <UIKit/UIKit.h>
#interface MainFeedTableViewController : UITableViewController
#end
In the MainFeedViewController.m (the one receiving the data) I have:
#import "FeedViewController.h"
#interface FeedViewController () <UINavigationBarDelegate>
#property (weak, nonatomic) IBOutlet UINavigationBar *navigationBar;
#end
#implementation FeedViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)setNavigationBarTitle:(NSString *)navigationBarTitle{
self.navigationItem.title = navigationBarTitle;
}
And in the MainFeedViewController.h I have:
#import <UIKit/UIKit.h>
#interface FeedViewController : UIViewController
#property NSString *navigationBarTitleToSet;
#end
I want to run the setNavigationBarTitle method with either data from the .h (navigationBarTitleToSet) or just from the sending view controller, if possible to run a method with delegation. Thanks a ton and I hope this is possible :)
It turns out I needed to add a second navigation bar to account for the container view, allowing me to navigate around the current stack with the parentViewController method and then navigationItem.title. For anyone who happens to find this with a container, make sure you add one immediately after the embed segue. I'm still not sure if you can use methods through delegation, but I can't ponder any situations where it would be necessary anymore, due to viewDidLoad. Thanks to #Tander for the help!

How can i make a UITabbar like this?

I would like to create a UITabbar like below but i don't know what is the logic to do that.
Here is the large answer:
First of all, you will need to create a UIView subclass to get a view that looks like the bar that you want. It can be composed by a background UIImageView and three buttons.
Then, the best thing would be to create a subclass of the UITabBarController and in its viewDidLoad or at any point where the flow will go through just once, you instantiate one view of type specified at first point. You should place the frame of this view in order to hide the original tabbar of the controller.
This would be the custom bar header file:
#interface CustomBar : UIView
{
}
#property (nonatomic, retain) UIImageView *backgroundView;
#property (nonatomic, retain) NSArray *buttons;
#end
You can easily complete the implementation. You can try to look for how to instantiate it with a nib file to make it easier to design it. In order to test, you can first just set the background color to green or something visible.
Then, this would be the subclass of the UITabBarController class:
#interface CustomTabBarController : UITabBarController
#property (nonatomic, retain) CustomBar *customBar;
#end
#implementation CustomTabBarController
- (void)viewDidLoad
{
[super viewDidLoad];
self.customBar = [[[CustomBar alloc] initWithFrame:[self.tabBar frame]] autorelease];
[self.view addSubview:self.customBar];
}
#end
Please, remember to implement the dealloc if you are not using ARC.
The thing I am not sorting out here is how to create the communication between the buttons from the custombar and the tabbarcontroller. This should be solved by delegates. If you need help with that, I will complete that too.
Good luck!

View not updating when a message is sent to its view controller

I am having problems updating a view when a message from another class is sent to a ViewController.
Basically I have an application with a single window where different custom views will be swapped out for another. I have an AppController Class that manages this and works fine:
#interface AppController : NSObject
#property (weak) IBOutlet NSView *ourView;
#property (strong) NSViewController *ourViewController;
- (IBAction)changeView:(id)sender;
- (IBAction)start:(id)sender;
- (void)changeViewContoller:(NSInteger)tag;
#end
When a new view is swapped out for another, the ourViewController property will be updated to point to that view's controller class. Every view controller class will have a method all named the same thing, for example "action". This method is supposed to change something on a view.
So the "start" method in AppController class will then call the "action" method on the ourViewController property. To do this I used the objc_msgSend() method:
objc_msgSend(self.ourViewController, action);
Here's the View Controller class definition:
#interface CountdownViewController : NSViewController
#property (weak) IBOutlet NSTextField *label;
- (IBAction)changeLabel:(id)sender;
- (void)start;
#end
I placed an NSLog() in the "action" method for each ViewController, to see if it was working, and it does, however the "action" method is also supposed to change a label's string value, but it does not. If anyone knows why the view is not being updated, that would be extremely helpful. Thanks!
the view is held weak?
TRY making it strong if you need to retain that pointer in this class
btw: ..also why do you objc_msgsend.... use performSelector

Using a protocol and a delegate

I am trying to get some code in a view working. I have declared a delegate and it does not get instantiated, any Idea what I am missing?
I have tried to summarise how I have done this below. I think that the issue is that somewhere, my dataSource delegate needs to be instantiated.
I have a View called graph view and a delegate that is in the viewcontroller GraphViewController.
I know that the method in GraphView is doing something as it calls the AxisDrawing helper class and draws in Axes.
Here is the relevant code
In GraphView.h I set up the protocol and the dataSourceDelegate
#class GraphView;
#protocol GraphViewDataSourceDelegate
- (float)functionOfX:(float)xValue inGraphView:(GraphView *)sender;
#end
#interface GraphView : UIView
#property(nonatomic, weak) IBOutlet id <GraphViewDataSourceDelegate> dataSourceDelegate;
#end
Which I synthesize in GraphView.m
#import "GraphView.h"
#import "AxesDrawer.h"
#implementation GraphView
#synthesize dataSourceDelegate = _dataSourceDelegate;
Also in Graph View I try to use the delegate as follows (pixel is a CGPoint). This routine does run and I can draw to GraphView from here provided I do not try to use my protocol method. i.e. Some of my DrawRect stuff does get drawn which checks out the linking of the UIView to my custom View
pixel.y = [self.dataSourceDelegate functionOfX:pixel.x inGraphView:self];
++++Breakpoint put in here+++++
In GraphViewController I state that I implement the protocol and implement it as follows. The compiler warns me and spots when the implementation is done, turning of the warning. (I am only returning 3.0 at the moment as a test).
#interface
GraphViewController () <GraphViewDataSourceDelegate>
#property (nonatomic, weak) IBOutlet GraphView *graphView;
#end
...
-(float) functionOfX:(float)xValue inGraphView:(GraphView *)sender{
NSLog(#"fofx accessed");
return 3.0;
}
If I look at the GraphView* object just after a breakpoint, it seems to have no instance. What am I missing out.
This is from the trace at the breakpoint
_dataSourceDelegate struct objc_object * 0x0
EDIT: (In addition to #Clays answer below)
It turned out that my connection to the custom view was broke. This meant that the fault lay with the View not talking to the Custom ViewController. The link is made by ctrl dragging from the View Controller button in the Interface builder to the View within the ViewController.
This caused the ViewController to be instantiated and everything then worked fine.
To put it another way.
The link in question was the Outlet. This has to be declared in the ViewController as a property and then linked in IB using ctrl Drag from the ViewController Name-Bar to the View in the Controller.
Provided that you have added the Outlet property correctly, when you do the ctrl drag, your view will appear an option.
The context button popup information on the ViewController button in IB does give a clue.
If you have neglected to put the outlet property into the view controller, the link does appear in the context button popup but it is greyed out, and when you do the link your View is not named.
Once you put the outlet in, the name appears in the menu but not greyed out.
Make sure you've hooked everything up correctly: setting your GraphView's dataSourceDelegate, and your GraphViewController's graphView.
From the trace it looks like you haven't done that; or you lose the reference somewhere along the way because at some point nothing's holding on to it.

How to change the text of a textField from another view

I have this code:
ViewController .h
#property IBOutlet UITextField *field;
ViewController .m
#synthesize field;
ViewControllerTwo .h
#import "ViewController.h"
{
ViewController *ViewCont;
}
-(IBAction)changeTextField
ViewControllerTwo .m
#import "ViewController.h"
-(IBAction)changeTextField{
viewCont.field.text = #"hello";
}
The problem is that it doesn't work, although it doesn't give me any error. Does anyone know what I'm doing wrong?
Never modify another view controller's views. You are encountering one of many problems doing that. In your case, the likely cause is that the other view controller has not yet loaded its view, so all the IBOutlets are still nil.
You're breaking MVC, and that's going to cause lots of little problems like this. Instead of having ViewControllerTwo modify the outlets of ViewController, you should move the data (#"hello") into a model object that is shared by both view controllers. ViewControllerTwo would write to it, and ViewController would read from it. You can share that model object by passing it to the view controllers as a property, or by making the model a singleton.
You aren't instantiating your instance of class ViewController, so you are essentially sending a message to nil.