I'm really going crazy on this.
But let me explain to you my little project first:
I have an Custom UITableView TDStartTableView.
Also in there I have some methods implemented for rendering the table. No problem there.
Inside of one TableViewCell there is a button.
When that Button is clicked it triggers this method:
- (void)triggerPush {
[self.delegate pushNextView];
}
self.delegate is specified in the .h file of TDStartTableView like this:
#property (nonatomic, weak) id<TDStartTableViewDelegate> delegate;
Also, the reference is set in my UITableViewController:
self.tableView.delegate = self;
So essentially what I'm trying to do is: Create a custom UITableView with Buttons etc. and then listen on the events from a ViewController that is implementing that UITableView and the protocol
So because the protocol forces me to implement pushNextView this method is in my UIViewController:
- (void)pushNextView {
NSLog(#"This works");
}
To this point everything works just fine, no problem there!
But now comes the tricky part.
I create a segue from my UIViewController to a new ViewController. I connect them via a segue and name the segue appropriately. pushToSecondStep.
Now one would think, that when I change the implementation of pushNextView to this
- (void)pushNextView {
[self performSegueWithIdentifier:#"pushToSecondStep" sender:self];
}
it works. But what I get is:
'Receiver (<TDFirstStepTableViewController: 0x8dc97d0>) has no segue with identifier 'pushToSecondStep''
Please help, I'm going crazy :D
The problem was, that I overwrote a constructor of TDStartTableView.
The proper form is that you implement all constructors, so that Objective-C can instantiate them all by itself:
- (id) initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
return self;
}
Also when you implement a custom UITableView Widget you shouldn't use UITableViewController but UIViewController.
Also you don't initialize your custom UITableView yourself, Storyboard already does that for you, so if you want to set special variables for it like numberOfRows then just declare a property and then set it via a setter-method outside and call [tableView reloadData].
Also, thanks https://stackoverflow.com/users/1095089/shubhank for helping me in the chat :)
Related
So I have this BaseCell class which also has this BaseCellViewModel. Of course on top of this lives some FancyViewController with FancyViewModel. The case here is that BaseCell has UIButton on it which triggers this IBAction method - that's fine and that's cool as I can do whatever I want there, but... I have no idea how should I let know FacyViewController about the fact that some action happened on BaseCell.
I can RACObserve a property in FancViewModel as it has NSArray of those cell view models, but how to monitor actual action and notify about exact action triggered on cell?
First thing that came to my mind is the delegation or notifications, but since we have RAC in our project it would be totally stupid not to use it, right?
[Edit] What I did so far...
So, it turns out youc can use RACCommand to actually handle UI events on specific button. In that case I've added:
#property (strong, nonatomic) RACCommand *showAction;
to my BaseCellViewModel with simple implementation like:
- (RACCommand *)showAction {
return [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
NSLog(#"TEST");
return [[RACSignal empty] logAll];
}];
}
And following this pattern I had to do something in my BaseCell which turned out to be quite simple and I ended up with adding:
- (void)configureWithViewModel:(JDLBasePostCellViewModel *)viewModel {
self.viewModel = viewModel;
self.actionButton.rac_command = self.viewModel.showAction;
}
And... It works! But...
I need to present UIActionSheet whenever this happens and this can be show only when I need the current parentViewController and since I don't have this kind of information passed anywhere I don't know what to do right now.
FancyViewModel holds a private #property (nonatomic, strong) NSMutableArray <BaseCellViewModel *> *cellViewModels;, but how can I register something on FancyViewController to actually listen for execution of RACCommand on BaseCellViewModel?
There are a few ways that the cell might communicate with the view controller. A common on is via delegation. Have the cell declare a public delegate, like:
// BaseCell.h
#protocol BaseCellDelegate;
#interface BaseCell : UITableViewCell
#property(nonatomic, weak) id<BaseCellDelegate> delegate;
// ...
#end
#protocol BaseCellDelegate <NSObject>
- (void)baseCell:(BaseCell *)cell didReceiveAction:(NSString *)actionName;
#end
When the button is pressed, work out what you'd like to tell the delegate, and tell it:
// BaseCell.m
- (IBAction)buttonWasPressed:(id)sender {
self.delegate baseCell:self didReceiveAction:#"someAction";
}
Then, in the view controller, declare that you conform to the protocol:
// FancyViewController.m
#interface FancyViewController () <BaseCellDelegate>
in cellForRowAtIndexPath, set the cell's delegate:
// dequeue, etc
cell.delegate = self;
You'll now be required to implement this in the vc:
- (void)baseCell:(BaseCell *)cell didReceiveAction:(NSString *)actionName {
// the cell got an action, but at what index path?
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
// now we can look up our model at self.model[indexPath.row]
}
UITableViewCell are volatile, reusable components. They come and go, and your button will do as well.
How about following #danh suggestion and once control is in View Controller, formulate a RAC signal programmatically.
Since I feel rather belonging to RxSwift camp :) I cannot provide source snippet, but this answer is probably what I meant.
As you already have a RACCommand in BaseCellViewModel, you can use one of its convenience signals. For example, you can track its state using executing signal:
[baseCellViewModel.showAction.executing subscribeNext:^(NSNumber *executing) {
//do something if the command is executing
}];
Bindings with RACObserve will work as well, if you need them.
You can also get the latest value from the command's underlying signal (but in the code you posted it won't work, as you use [RACSignal empty] with doesn't send any 'next' values):
[[baseCellViewModel.command.executionSignals switchToLatest] subscribeNext:^(id x) {
//do something with the value
}];
Note that you should subscribe to this signals when you create BaseCellViewModel, not in configureWithViewModel as the latter will be called many times (resulting in many subscriptions for the same signal).
I have a UITableView comprised of custom UITableViewCells. In each cell, there is a UILabel and a UISlider. Does anyone know how to, upon a change in value of one of the sliders, send the new value of the slider from the custom UITableViewCell (in a separate file) to the UITableViewController, so that I can then update the array from which the table was populated?
The closest I've got so far is a failed hack: firing a setSelected event when a slider value is changed. Whilst this highlights the changed custom cell, the event is not picked up by didSelectRowAtIndexPath in the UITableViewController.
Whilst code is always appreciated, a conceptual/method solution is what I am looking for.
Thank you in advance,
Jamie
What you need is called Delegate Pattern.
Quoting from there to explain what does it mean:
Delegation is a simple and powerful pattern in which one object in a
program acts on behalf of, or in coordination with, another object.
The delegating object keeps a reference to the other object—the
delegate—and at the appropriate time sends a message to it. The
message informs the delegate of an event that the delegating object is
about to handle or has just handled. The delegate may respond to the
message by updating the appearance or state of itself or other objects
in the application, and in some cases it can return a value that
affects how an impending event is handled. The main value of
delegation is that it allows you to easily customize the behavior of
several objects in one central object.
These diagrams will help you understand what goes on:
Architecture:
Operation:
Now as to how to implement it, this is what you have to do.
For Objective-C:
First of all, create delegate methods of your UITableViewCell. Lets name it ContactTableViewCell.
In your ContactTableViewCell.h file, do this:
#protocol ContactCellDelegate <NSObject>
#required
-(void) didMoveSliderWithValue:(float) value;
#end
#interface ContactTableViewCell : UITableViewCell
#property (weak, nonatomic) id<ContactCellDelegate> delegate;
Now conform your TableViewController to this delegate. Let's name your VC MyTableViewController.
In MyTableViewController.h, Do this:
#interface MyTableViewController : UIViewController <ContactCellDelegate> //Use UITableViewController if you are using that instead of UIViewController.
In your cellForRowAtIndexPath, before returning cell, add this line:
cell.delegate = self;
Add implementation for the delegate method INSIDE your MyTableViewController.m.
-(void) didMoveSliderWithValue: (float) value
{
NSLog(#"Value is : %f",value);
//Do whatever you need to do with the value after receiving it in your VC
}
Now let's get back to your ContactTableViewCell.m. In that file you must have added some IBAction to capture the value change event in slider. Let's say it is the following:
- (IBAction)sliderValueChanged:(UISlider *)sender {
self.myTextLabel.text = [#((int)sender.value) stringValue]; //Do whatever you need to do in cell.
//Now call delegate method which will send value to your view controller:
[delegate didMoveSliderWithValue:sender.value];
}
When you call delegate method, it will run the implementation that we wrote earlier in the MyTableViewController. Do whatever you need in that method.
What happens here is that your Cell sends the message to your desired VC (Which is delegate of the Cell), that "Hey, Call the delegate method that we wrote earlier in your body. I am sending you parameters right away". Your VC takes the parameters and does whatever you wanted it to do with that info and at that time.
For Swift:
First of all, your TableViewCell.swift file, create a protocol like this:
#class_protocol protocol ContactCellDelegate {
func didMoveSliderWithValue(value: Float)
}
Now in your Cell class, create a delegate property like:
var cellDelegate: ContactCellDelegate?
In your Slider IBAction, call the delegate method like this:
self.cellDelegate?.didMoveSliderWithValue(slider.value)
In your VC do these changes:
Make it conform to the delegate:
class MyTableViewController: UIViewController, ContactCellDelegate
Add this line before returning cell in cellForRowAtIndexPath
cell.cellDelegate = self //Dont forget to make it conform to the delegate method
Add the implementation of required delegate method:
func didMoveSliderWithValue(value:float) {
//do what you want
}
I have kept the Swift part precise and summarized because It should be very easy to change the detailed Obj-C explanation to Swift implementation. However If you are confused about any of the pointers above, leave a comment.
Also see: StackOverflow answer on using Delegate pattern to pass data back
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 9 years ago.
Improve this question
I have a custom UIView which has been loaded into a different UIViewController. I have a button in the custom UIView, which calls a method inside its own class. I want this method to refresh the UIViewController which the UIView is embeded in. Currently I have tried importing the UIViewController to the UIView initializing it and then calling a public method which i have put inside the UIViewController. Nothing that happens inside this method seems to affect it though, I have even tried changing the navigation bar title and that wont work. I know the method is getting called though as it comes up in my log.
Any help?
This is where a delegation pattern comes into action. If I have not misunderstood, you want to perform some action(refresh??) on a UIViewController based on some action in a UIView, which is in turn is a part of its own view hierarchy.
Lets say your custom view is wrapped in a class CustomView. It has a method named action that is invoked at some point. Moreover, lets assume that you have used CustomView instances to some view controllers, namely, MyViewController1, MyViewController2, etc, as a part of their view hierarchies. Now, you want to perform some action (refresh) in your VCS when action method is triggered from your CustomView instances. For this purpose, you need to declare a protocol in the header file of your CustomView and will have to register a handler of this protocol (commonly known as delegate) to an instance of the CustomView. The header file would look something like this:
//CustomView.h
//your include headers...
#class CustomView;
#protocol CustomViewDelegate <NSObject>
-(void)customViewDidPerformAction:(CustomView*)customView;
#end
#interface CustomView : UIView {
//......... your ivars
id<CustomViewDelegate> _delegate;
}
//.......your properties
#property(nonatomic,weak) id<CustomViewDelegate> delegate;
-(void)action;
//....other declarations
#end
Now, you would like customViewDidPerformAction method to be called from any class (like a view controller for "refresh" purpose) whenever action method is triggered. For that purpose, in your implementation file (CustomView.m), you need to invoke the customViewDidPerformAction method stub, if available, from inside your action method:
//CustomView.m
//.......initializer and other codes
-(void)action {
//other codes
//raise a callback notifying action has been performed
if (self.delegate && [self.delegate respondsToSelector:#selector(customViewDidPerformAction:)]) {
[self.delegate customViewDidPerformAction:self];
}
}
Now, any class that conforms to CustomViewDelegate protocol can register itself as a receiver of the callback customViewDidPerformAction:. For example, lets say, our MyViewController1 view controller class conforms to the protocol. So the header to the class would look something like this:
//MyViewController1.h
//include headers
#interface MyViewController1 : UIViewController<CustomViewDelegate>
//........your properties, methods, etc
#property(nonatomic,strong) CustomView* myCustomView;
//......
#end
Then you need to register this class as a delegate of myCustomView, after instantiating myCustomView. Say you have instantiated myCustomView in the viewDidLoad method of the VC. So the method body would be something similar to:
-(void)viewDidLoad {
////...........other codes
CustomView* cv = [[CustomView alloc] initWithFrame:<# your frame size #>];
cv.delegate = self;
self.myCustomView = cv;
[cv release];
////......other codes
}
Then you also need to create a method with the same method signature as the protocol declares, inside the implementation(.m) file of the same VC and right your "refresh" code there:
-(void)customViewDidPerformAction:(CustomView*)customView {
///write your refresh code here
}
And you are all set. MyViewController1 would execute your refresh code whenever action method is performed by CustomView.
You can follow the same mechanism (conform to CustomViewDelegate protocol and implement customViewDidPerformAction: method) from within any VC, containing a CustomView in its view hierarchy, to refresh itself whenever the action is triggered.
Hope it helps.
Suppose you implement a custom table view and a custom view controller (which mostly mimics UITableViewControllers behaviour, but when initialized programmatically, ...
#interface Foo : MyCustomTableViewController ...
Foo *foo = [[Foo alloc] init];
... foo.view is kind of class MyCustomTableView instead of UITableView:
// MyCustomTableView.h
#protocol MyTableViewDelegate <NSObject, UITableViewDelegate>
// ...
#end
#protocol MyTableViewDataSource <NSObject, UITableViewDataSource>
// ...
#end
#interface MyCustomTableView : UITableView
// ...
#end
// MyCustomTableViewController.h
#interface MyCustomTableViewController : UIViewController
// ...
#end
How should you implement/override init methods in correct order/ways so that you could create and use an instance of MyCustomTableView both by subclassing MyCustomTableViewController programmatically or from any custom nib file by setting custom class type to MyCustomTableView in Interface Builder?
It important to note that this is exactly how UITableView (mostly UIKit for that matter) works right now: a developer could create and use either programmatically or by creating from nib, whether be it File owner's main view or some subview in a more complex hierarchy, just assign data source or delegate and you're good to go...
So far I managed to get this working if you subclass MyCustomTableViewController, where I will create an instance of MyCustomTableView and assign it to self.view in loadView method; but couldn't figure out how initWithNibName:bundle:, initWithCoder:, awakeFromNib, awakeAfterUsingCoder:, or whatever else operates. I am lost in life cycle chain and end up with a black view/screen each time.
Thanks.
It is a real mystery how the UITableViewController loads its table regardless of if one is hooked up in interface builder, however I have came up with a pretty good way to simulate that behavior.
I wanted to achieve this with a reusable view controller that contains a MKMapView, and I figured out a trick to make it happen by checking the background color of the view.
The reason this was hard is because any call to self.view caused the storyboard one to load or load a default UIView if didnt exist. There was no way to figure out if inbetween those 2 steps if the user really didn't set a view. So the trick is the one that comes from a storyboard has a color, the default one is nil color.
So now I have a mapViewController that can be used in code or in storyboard and doesn't even care if a map was set or not. Pretty cool.
- (void)viewDidLoad
{
[super viewDidLoad];
//magic to work without a view set in the storboard or in code.
//check if a view has been set in the storyboard, like what UITableViewController does.
//check if don't have a map view
if(![self.view isKindOfClass:[MKMapView class]]){
//check if the default view was loaded. Default view always has no background color.
if([self.view isKindOfClass:[UIView class]] && !self.view.backgroundColor){
//switch it for a map view
self.view = [[MKMapView alloc] initWithFrame:CGRectZero];
self.mapView.delegate = self;
}else{
[NSException raise:#"MapViewController didn't find a map view" format:#"Found a %#", self.view.class];
}
}
The strategy I've used when writing such classes has been to postpone my custom initialization code as late as possible. If I can wait for viewDidLoad or viewWillAppear to do any setup, and not write any custom code in init, initWithNibName:bundle: or similar methods I'll know that my object is initialized just like the parent class no mater what way it was instantiated. Frequently I manage to write my classes without any overrides of these init methods.
If I find that I need to put my initialization code in the init methods my strategy is to write just one version of my initialization code, put that in a separate method, and then override all the init methods. The overridden methods call the superclass version of themselves, check for success, then call my internal initialization method.
If these strategies fail, such that it really makes a difference what way an object of this class is instantiated, I'll write custom methods for each of the various init methods.
This is how I solved my own issue:
- (void)loadView
{
if (self.nibName) {
// although docs states "Your custom implementation of this method should not call super.", I am doing it instead of loading from nib manually, because I am too lazy ;-)
[super loadView];
}
else {
self.view = // ... whatever UIView you'd like to create
}
}
My goal is to notify a UITableView to refresh itself every time some configurations have changed. The problem is that the configuration view is "not" on the same view that produces the signal. (Yes, I used Tabbed Application.)
Currently I use a sort of global variable in AppDelegate for detecting the change in one view, and do the check in another view. This is fine but the code is not readable as it is so tightly coupling. Is there an elegant method for doing this? Do I miss something in this programming framework?
If there were such an elegant way, I suppose the refreshing process of UITableView should happen as soon as the notification occurs. In this case, I would like to know whether it's possible to delay UITableView from refreshing itself until viewDidAppear occurs.
I would use KVO (Key Value Observing) to keep track of when it changes:
- (void)viewDidLoad {
[super viewDidLoad];
// Note that you can use the options to get the new value passed when it
// changes if you want to update immediately.
[configurationObject addObserver:self forKeyPath:#"configurationItem" options:0 context:nil];
}
- (void)viewDidUnload {
[super viewDidUnload];
[configurationObject removeObserver:self forKeyPath:#"configurationItem"];
}
// Note that I would refresh in viewWillAppear instead of viewDidAppear
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (self.needToRefreshData == YES) {
[self.tableView refreshData];
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (keyPath isEqualToString:#"configurationItem") {
[self.needToRefreshData = YES];
}
}
Use Delegation Design Pattern to pass data from one View Controller to the Other.
For example, let's say one Tab shows a list of cars in a UITableViewController and you have another view that let's a user add a new car to the list. You can let the UITableViewController
Adopt AddCarViewController's protocol
Set itself as a Delegate for AddCarViewController's protocol
Implement its protocol method
Execute the protocol method when informed
You can then let the AddCarViewController
Create a Protocol
Declare object reference Delegate with getter and setter methods
Define a method under that protocol
Inform the Delegate when the Save action is performed
Take a look at the following sample code for your UITableViewController
#interface ViewController : UITableViewController <AddCarViewControllerDelegate>
:
:
// The addCar: method is invoked when the user taps the Add button created at run time.
- (void)addCar:(id)sender
{
// Perform the segue named ShowAddCar
[self performSegueWithIdentifier:#"ShowAddCar" sender:self];
}
:
:
// This method is called by the system whenever you invoke the method performSegueWithIdentifier:sender:
// You never call this method. It is invoked by the system.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSString *segueIdentifier = [segue identifier];
if ([segueIdentifier isEqualToString:#"ShowAddCar"]) {
// Obtain the object reference of the destination view controller
AddCarViewController *addCarViewController = [segue destinationViewController];
// Under the Delegation Design Pattern, set the addCarViewController's delegate to be self
addCarViewController.delegate = self;
// Instantiate a Save button to invoke the save: method when tapped
UIBarButtonItem *saveButton = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemSave
target:addCarViewController action:#selector(save:)];
// Set up the Save custom button on the right of the navigation bar
addCarViewController.navigationItem.rightBarButtonItem = saveButton;
}
}
:
:
- (void)addCarViewController:(AddCarViewController *)controller didFinishWithSave: (BOOL)save {
:
:
}
Sample code for the AddCarViewController is here
#protocol AddCarViewControllerDelegate;
#interface AddCarViewController : UIViewController
#property (nonatomic, strong) IBOutlet UITextField *carMake;
#property (nonatomic, strong) IBOutlet UITextField *CarName;
#property (nonatomic, assign) id <AddCarViewControllerDelegate> delegate;
// The keyboardDone: method is invoked when the user taps Done on the keyboard
- (IBAction)keyboardDone:(id)sender;
// The save: method is invoked when the user taps the Save button created at run time.
- (void)save:(id)sender;
#end
/*
The Protocol must be specified after the Interface specification is ended.
Guidelines:
- Create a protocol name as ClassNameDelegate as we did above.
- Create a protocol method name starting with the name of the class defining the protocol.
- Make the first method parameter to be the object reference of the caller as we did below.
*/
#protocol AddCarViewControllerDelegate
- (void)addCarViewController:(AddCarViewController *)controller didFinishWithSave:(BOOL)save;
#end
Well, one approach would be to have some common class (singleton perhaps which app delegate kind of is) that keeps track of your model, when the settings viewController detects a change it can mark the model as changed, then when the view in question comes in to view, ie, viewDidAppear gets called, it can query the model to see if the changed flag has been set, if it has then you know to reload the table view, otherwise you dont...
Another way could be to use notification center for it, if your view is loaded it can sign up for the notifications of the model change, in which at point it sets a flag that it needs to reload the table view next time it comes on screen..
hope this helps
You could store the configuration in core data and use an NSFetchedResultsController with the dependant view controller set as a delegate. This way your view controller will get a callback whenever the data is changed.
Apple has some boilerplate code to handle the updates as well