Ok so this is the situation I have.
I have created new project using master detail template and in MasterViewController I have table and cell which was displaying title only. I modified cell to be custom and added two text-fields. My app requirement is to plot graph real-time as soon user enters point in textfield. Graph is displayed in detail view and that part works fine (Graph plotting).
Now my problem is how to transfer data from the class which is not uiview controller. MasterViewController is subclass of UITableViewController but to create outlets for the textfields I have to have subclass of UITableViewCell. So I created new class CellData (subclass of UITableViewCell) and I can get values from textfields real-time using IBAction didFinishEditing and IBOutlets for the textfields and data is automatically added to array.
Now how should I push this array to MasterViewController and then further to class GraphDraw which will actually draw graph. What I did is to create global class which I suspect is not ideal solution. Should I create three way protocol and delegate solution or maybe model class as someone suggested?
So far all the protocol and delegate tutorials were focused on viewControllers and I have no problems with that but they don't quite work for my situation. And with model class which is subclass of NSObject I cannot directly get cell data. Now I apologise if question is long but I am beginner and I am trying to figure out best way to solve these problems. Any help is appreciated.
Delegation is the way to go here. This setup though is a little confusing and is probably why the resources out there are not exactly what you are looking for.
First you will need a delegate that can send messages from your UITableViewCell subclass to the master UITableViewController. Take the following for example:
CellData
#protocol JDPlotCellDelegate <NSObject>
- (void)didFinishEditingWithData:(id)someData;
#end
Now when the UITextField sends its didFinishEditing delegate message:
- (void)textFieldDidEndEditing:(UITextField *)textField {
[self.delegate didFinishEditingWithData:textField.text];
}
Simple so far right?
Next, your master tableview controller needs to implement the delegate (and make sure the cells have the delegate property hooked up!)
MasterViewController
- (void)didFinishEditingWithData:(id)someData {
[self.detailViewController plotSomeData:someData];
}
Then since the master has a reference to the details view controller it can be as simple as declaring a public method - (void)plotSomeData:(id)someData; on the detail view controller that can take the data and plot it.
Hope this helps!
if you are not willing to use delegates its ok, I mean you could use NSNotifications to tell other listeners in the app that some event has occurred and you could also piggy back some data with the notifications.
In the reciever of the notfication -viewDidLoad perhaps add this:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(masterEventOccurred:) name:#"MasterEventNotfication" object:nil];
Add this function in the listener class
-(void)masterEventOccurred:(NSNotification *)notification
{
//notification happened
NSLog(#"object sent: %#",notification.object);
}
In dealloc of listener class remove notification observer
-(void) dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
Now everything is set up we have to fire the event, add the below lines wherever you want in the master class(according to you, may be in textField delegate method):
[[NSNotificationCenter defaultCenter] postNotificationName:#"MasterEventNotfication" object:yourTextField.text];
Related
I'm trying to category UIViewController to override viewWillAppear:. But getting this warning.
Category is implementing a method which also be implemented in primary
class
#implementation UIViewController (ViewWillAppearCategory)
-(void)viewWillAppear:(BOOL)animated
{
//.........
}
#end
I want to do some stuff during view appear in all screen, So I don't want to touch in all screen. that's why, go with category.
I may implement some method in sub class and I can call that method in all VC(all Screen). But I don't want this. It automatically invoke in view will appear call. Is this any idea to do this or did any mistake in above?
Note: This code will only appear in development phase for some testing purpose. So I'll remove this code when go with app store. So It should be easier task during removal, that is I won't touch all screen. I won't keep this code during submission to app store.
In such cases you must try Method Swizzling, a very nice formed concept which allows you to change the implementation of an existing selector.
For more details and code please visit the link below.
http://nshipster.com/method-swizzling/
categories are for adding new methods, not overriding existing ones. Maybe make a subclass of UIViewController, say, MyUIViewController, with this code:
-(void) viewWillAppear:(BOOL) animated {
// do your "category" stuff
}
then make all your other UIViewControllers subclasses of MyUIViewController with this code:
-(void) viewWillAppear:(BOOL) animated {
[super viewWillAppear:animated];
// rest of code for this class
}
I understand the reasons why you want to have a simple solution to test something on all screens and remove it easily, however:
You can not call super in a category, and not calling [super viewWillAppear:] may have unexpected results depending on the class and its particular implementation.
Swizzling methods is a hack and as you'll remove it from your final version, your testing version becomes useless as it may behave very differently.
On the other hand creating a UIViewController superclass where you properly override viewWillAppear: is not that complicated:
The code will belong only to a single class. No need to repeat/maintain code for every "screen".
You only need to change the other controllers' superclass and Nibs or Storyboards references once.
You can keep the superclass for both testing and release and the behavior will be similar.
You can do so many more things in a superclass than in a category.
Ultimately it would be interesting to know what are you trying to achieve. You could probably achieve similar things by implementing a UINavigationControllerDelegate and keep track of controllers getting pushed and popped.
As for viewWillAppear documentation:
This method is called before the receiver’s view is about to be added
to a view hierarchy and before any animations are configured for
showing the view. You can override this method to perform custom tasks
associated with displaying the view. For example, you might use this
method to change the orientation or style of the status bar to
coordinate with the orientation or style of the view being presented.
If you override this method, you must call super at some point in your implementation.
Again, you can't do that from a category.
As you are saying the code is going to be executed only in debug mode. Then why do you worry about warnings let the warning come you continue your work when it comes to release you remove your Category.
If you don't even need to see the warning your go with your same answer like
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
-(void)viewWillAppear:(BOOL)animated
{
NSLog(#"I get callback here too");
}
#pragma clang diagnostic pop
But I would say to go for subclassing because removing the existing class is also not that hard in XCode tool.
What you want to achieve defeats the purpose of a Category. However, there's another way aside from subclassing UIViewController but you have to touch the viewWillAppear method for each controller.
//UIViewController+CustomCategory.h
#interface UIViewConctroller (CustomCategory)
- (void)performCustomization;
#end
//UIViewController+CustomCategory.m
#implementation UIViewController (CustomCategory)
- (void)performCustomization {
// Do custom stuff…
}
#end
Then in each controller
//MYViewController.m
#import "UIViewController+CustomCategory.h"
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self performCustomization];
}
I have a question concerning iOS. I couldn't find an answer to my question online but maybe one of you know the answer.
Im working on an app which pulls data from an online database. A NSObject Class is called by a timer in the background of the app. I want it to tell one of my view controller to reload its tableview if it finds new data. Like it is supposed to check for data not caring in which view controller im in. But when I'm in the specific one with my table view it should reload the table view if it found new data.
I've tried to create an instance of the tableviewcontroller in my NSObject and call the reload table view function front there, but that doesn't work :/
I apologise for my poor explanation, I'm not a native speaker.
Thank you very much :)
You can use two approaches:
post a NSNotification in your class that gets more data and observes
this notification in your class that the TableView is initialized.
make a delegate approach between the two classes and fire a method when you have the new data.
I would go with the first option:
a. in your data loading class:
NSNotification *notification = [NSNotification notificationWithName:#"newDataFetched" object:anyObject];
[[NSNotificationCenter defaultCenter] postNotification:notification];
b. in your listener class:
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(aMethodToReloadTheTableView:)
name:#"newDataFetched"
object:nil];
I'm creating an iOS app with 4 tabs (4 view controllers) that gets CoreLocation updates and displays the location along with some other data in various representations (table view, map view, etc)
I only want to use one CoreLocationManager, so I can only have one delegate, but 4 view controllers need to know about the updates so that the visible view can be updated
What is the best way to let my view controllers know that there has been a location update?
The simplest is to post a notification rather than using a delegate. Delegates are 1:1 where as notifications are 1:many. Problem is you still need at least one delegate which will post the notifications.
If you want, you can create an NSProxy object which you can set as the location manager delegate and which internally holds a list of other delegates and forwards all of the received method calls to all of the internally managed delegates.
The BEST method is probably to simply use the notification center. Rather than a #protocol, put a NSString * const you're using for the notification:
NSString * const kMyNotificationString #"MyNotificationString"
Now, when the would-be delegates need to respond, it's as simple as:
[[NSNotificationCenter defaultCenter] postNotificationName:kMyNotificationString
object:nil];
Any object that needs to respond to this notification can #import this header file (so it gets the NSString * const-ed notification name and then and start listening to the notification as such:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(myMethod)
name:kMyNotificationString
object:nil];
Just don't forget to remove observers when (or before) you dealloc.
Alternatively, there's this approach... but it's not really that great, and I'd actually recommend against it.
Rather than a single delegate property, you could have a NSArray property that handles the "delegates" (make sure you have an "addDelegate" method that only adds weak references to the array). Then, when you need all of these objects to respond to a change:
for (id<MyProtocol> object in self.arrayOfDelegates) {
if ([object respondsToSelector:#selector(myMethod)]) {
[object myMethod];
}
}
The easiest way is implement one delegate that forwards messages as notifications, this way any number of objects can subscribe as observers for those notifications.
I'm very new to objective-c and programming in general, and just started building a tab-based app in xcode. I have three view controllers and a slider in each of the views. I want the sliders in the second and third view to copy the position (and therefore the value) of the slider in the first view - and vice versa. So that irrespective of which view the user is at, it looks as though there is only one slider throughout.
I’m pretty sure there must be a simpler way to go about this. I hope I’ve explained my issue adequately.
Any advice would be greatly appreciated.
There are a few solutions you can implement.
You can create one slider and have a reference to it in some type of singleton object like the AppDelegate and pass it around to the respective views adding as a subview. Since a view can only have one super view your are safe.
You can use NSNotifications to notify each of the sliders that a value has changed. NSNotifications allow you to pass in an NSDictionary call userInfo so you can pass values around.
My personal opinion is that number 1 is cleaner, NSNotifications are heavy and there might be a slight delay on when things actually get updated.
Add following target to your slider
- (IBAction)sliderValueChanged:(UISlider *)sender
{
// fire notification here
[[NSNotificationCenter defaultCenter] postNotificationName:#"SliderValueChanged" object:nil];
}
and in your other view controllers, you can add observer for "SliderValueChanged"
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(onSliderValueChanged:)
name:"SliderValueChanged" object:nil];
- (void)onSliderValueChanged:(NSNotification *)notification
{
// Adjust your respective view controller's slider here
}
Another approach is to store your UISlider value in NSUserDefaults when it changes. Now in every controllers ViewWillAppear method you can get that value from NSUserDefaults and set it for your UISlider. That way no matter which controller the user is looking at it will show the same value for them.
I have a superview and I add a subview to make a selection. In the superview (main view) I do the following:
[self.view addSubview:cityViewController.view];
In the cityView, when I have done what I need to do, I just do
self.view removeFromSuperView.
The question is, from within the superview, how can I tell when the subview has removed itself.
There's a few ways, but honestly since the current view controller (let's call it main) is just adding the cityViewController's view, keep the handling of adding/removing the views to the current view controller, and just have the main controller call [cityViewController.view removeFromSuperView]
This way you can execute whatever code you want when it receives this notification (be it a method that fires or a UINotification).
-- edit for sample UINotification code --
main.m
...
//Define cityViewController as an iVar and alloc/init it
[[UINotificationCenter defaultCenter] addObserver:self selector:#selector(didFinishView:) name:#"DidFinishView" object:nil];
[self.view addSubview:cityViewController.view];
...
-(void) didFinishView:(NSNotification *)notification {
[cityViewController.view removeFromSuperView];
}
CityViewController.m
-(IBAction) doneButtonClick:(id) sender {
[[NSNotificationCenter defaultCenter] postNotificationName:#"DidFinishView" object:nil];
}
The quick answer is your view should not be removing itself. It's better practice for a view to communicate user interactions to a relevant controller through an interobject communication mechanism. The most common methods are direct messaging, protocols and notifications. The iOS framework uses all of these and there are great docs explaining them. Here's a brief summary:
Direct messaging. Use this when an object needs to communicate with a specific object of a known type. For example, if MyView is always contained in MyViewController and needs to send messages to it you can add a property to the MyView class that keeps a pointer to the specific MyViewController object. You can then send a message from myView to it's myViewController via [myView.myViewController userDidTapSaveButton] or whatever.
Protocols. A protocol defines a contract between objects that don't know anything about each other other than that they abide by the contract. For example, UITableView knows that it's delegate conforms to the UITableViewDelegate protocol and it can send the required protocol messages to it's delegate. Any object can conform to the UITableViewDelegate protocol.
Notifications. Notifications allows an object to post notifications through a central mechanism (NSNotificationCenter) that other objects can observe and respond to. Notifications are useful when the object posting the notification doesn't know or care what objects are observing it's notifications.
I'd read the relevant docs and other Q&A on SO about these methods. I'd also study up a bit on the MVC (Model/View/Controller) design pattern so you get more comfortable knowing where to put app logic. Generally, a view should only be responsible for it's display (based on properties set by it's controller), observing/responding to user actions, and notifying it's controller for relevant actions.