Custom UIView inside a UIViewController button click to refresh UIViewController [closed] - ios

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.

Related

Getting data out of custom UITableViewCell and back into UITableViewController

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

Call method for different UIViewController

I am making master detail application, i have dynamic Detail ViewController. Detail ViewController are changed.
But in every Detail ViewController I have one common method updateInfo I want to call that method
Here is my code
UINavigationController *nav=[self.splitViewController.viewControllers objectAtIndex:1];
UIViewController *controller=[nav.viewControllers objectAtIndex:0];
[controller updateLastInfo];
But it gives me error no method found.
it will work if i use UIViewController name.
HomeViewController *controller=(HomeViewController)[nav.viewControllers objectAtIndex:0];
[controller updateLastInfo];
But i dnt want to do above things.
I have tried to explain. Please help
You can use id
UINavigationController *nav=[self.splitViewController.viewControllers objectAtIndex:1];
id controller=[nav.viewControllers objectAtIndex:0];
[controller updateLastInfo];
You could subclass UIViewController and make a base DetailViewController class that houses common functionality of your detail view controllers. Then you would make all of your detail view controllers subclass DetailViewController instead of UIViewController. This would be a safe way to do it and would also allow you to add extra functionality to your updateInfo method in the specific detail view controllers.
If you want an unsafe way, you could make your controller object of type id. I wouldn't suggest this approach as it relies on your personal knowledge of the code. If someone else (or yourself down the road) sets it to a view controller that doesn't have that method, the code will still try to run and will crash.
UIViewController doesn't have a method named updateInfo, so the compiler will of course complain when you try to send that message to a pointer that's known only to point to an instance of UIViewController. When you use the class name, like this:
HomeViewController *controller=(HomeViewController)[nav.viewControllers objectAtIndex:0];
you're providing more information to the compiler, using a type cast to tell it "Hey, don't worry, I know for certain that the object I'll get back is a HomeViewController. Since you seem to have several types of view controllers that all have this method, the best thing to do is to declare the updateInfo method in a protocol and then have each of those UIViewController subclasses implement that protocol. So, your protocol declaration would be in a header file and might look like:
#protocol SomeProtocol
- (void)updateInfo
#end
and each class that has an -updateInfo method would just need to declare that it adopts the protocol:
#interface HomeViewController <SomeProtocol>
//...
#end
and then make sure that you have an -updateInfo in your class implementation:
#implementation HomeViewController
- (void)updateInfo {
//...
}
//...
#end
Then, in your code, you can either check that the object conforms to the protocol using -conformsToProtocol: like this:
if ([controller conformsToProtocol:#protocol(SomeProtocol)]) {
UIViewController<SomeProtocol> *c = (UIViewController<SomeProtocol>*)controller;
[c updateInfo];
}
or else just check that the object responds to the selector before calling it:
if ([controller respondsToSelector:#selector(updateInfo)]) {
[controller performSelector(updateInfo)];
}
The other answers you've received (using id or creating a common base class) are also good ones, but to be safe make sure you do some checking before calling your method. For example, you can use -isKindOfClass to make sure that the view controller you get back is in fact an instance of your common base class, and you can use -respondsToSelector: as above to check that an id points to an object that implements updateInfo.

After Buttonclick delegation: (ViewController) has no segue with identifier 'pushToSecondStep'

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 :)

UIButton target to be UIViewController not custom UIView

I have a UIViewController which creates a custom sub view. The sub view is a UIView object which has been subclassed a few times.
Within the subview class I create a custom init method:
-(id)init {
self = [super init];
if (self) {
// Init code
[self spm_correctGuessViewCustomInit];
}
return self;
}
And within this I create a button and a label. The question relates directly to the button and its target action.
What I would like is for the UIViewController to have the buttons action, not the subclasses UIView (which actually creates and holds the button).
[continueButton addTarget:self.superview action:#selector(correctGuessContinueButtonPressed) forControlEvents:UIControlEventTouchUpInside];
I pass in the target of self.superview, this appears to work correctly and the correct method is run. However, I am shown a warning in the subclass 'Undeclared selector 'correctGuessContinueButtonPressed''
So am I implementing this approach correctly? Please let me know if more information is required.
One solution would be to update your custom view's init method so it takes target and action parameters (much like the addTarget: method of the button). You could then pass these values to the button via the addTarget: call.
- (instancetype)initWithTarget:(id)target action:(SEL)action {
// your normal init code here
// use target and action to setup your button
}
Your view.superview approach will not bring you to the view controller, but to a view.
You can import the header of your implementing class to fix the warning, but I think your design should be improved. Views should work pretty much on their own and not depend on their superviews, or even worse the whole architecture of views and controllers.
I'd pass a delegate down the line that gets called when the user pressed the button, or set some blocks on the views that get called when buttons fire.
Avoid communication over several layers of abstraction.
Reference previous similar question: Calling a method in a UIViewController from a UIButton in a subview
I had to add an import to the View Controller that the method was on, within the custom UIView subclass.
With the controller property set I could set the button target as controller
Ensure that the method that was being called from the button was in the controller header file, so could be seen by the subview implementation file. Previously this was not so the subview was not to know this existed.

How to initialize a custom view(controller) so it works both programmatically and in Interface Builder?

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
}
}

Resources