Confused about addTarget pointer behavior for addTarget:action:forControlEvents: - ios

I have a UIView subclass with a delegate property. In the init method, I set
self.delegate = nil.
The view also has a button, so in the init method, I also set the target of the button to be self.delegate, which is nil:
[myButton addTarget:self.delegate action:#selector(buttonAction) forControlEvents:UIControlEventTouchUpInside]
In the UIViewController that sets up my UIView subclass, I call a method in the UIView that sets the UIView's self.delegate to the UIViewController. When I click the button, the change in target seems to be reflected.
I am wondering how this ends up working, as my understanding is that addTarget:action:forControlEvents takes an id as the target, and pointers should be pass by value in Obj-C. Thus, I am pretty confused about why the originally nil-valued pointer was updated after the addTarget method was already called.

The right way to do that is declaring a protocol for your view, which will delegate for button's tap action, i.e.
YourView.h
#class YourView;
#protocol YourViewDelegate
#optional
- (void)customView:(YourView *)view didSelectButton:(id)button;
#end
#interface YourView : UIView
//...
#property (weak, nonatomic) id <YourViewDelegate> delegate;
#end
YourView.m
#interface YourView()
#end
#implementation
- (id)init
{
if (self = [super init]) {
//...
[self setup];
}
return self;
}
- (void)awakeFromNib
{
//...
// setup logic when this view created from storyboard
[self setup];
}
- (void)setup
{
[myButton addTarget:self
action:#selector(buttonTapped:)
forControlEvents:UIControlEventTouchUpInside];
}
- (void)buttonTapped:(id)sender
{
if (self.delegate && [self.delegate respondsToSelector:#selector(customVIew:didSelectButton)] {
[self.delegate customView:self didSelectButton:sender];
}
}
#end
Then, in your view controller implement YourViewDelegate category:
#interface YourViewController()
//...
#end
#implementation
- (void)viewDidLoad
{
[super viewDidLoad];
//...
self.yourView.delegate = self;
}
//...
- (void)customView:(YourView *)view didSelectButton:(id)button
{
//do your stuff
}
#end

Objective-C uses Dynamic binding. Method to invoke is determined at runtime instead of at compile time. Which is why it is also referred to as late binding.
Reference link -
https://developer.apple.com/library/ios/documentation/general/conceptual/DevPedia-CocoaCore/DynamicBinding.html
So what will be the delegate and which method is being called is defined at runtime.

Related

Unable to call method from one class to another class while using ARC

I know how to call a method of one class to another class. However This time its not working for me and its just driving me nuts. Below is my code
MenuPageCell.h
#import <UIKit/UIKit.h>
#class MenuPageViewController;
#interface MenuPageCell : UITableViewCell{
NSInteger m_cellIndex;
MenuPageViewController *m_parentViewController;
}
#property(nonatomic, assign) NSInteger m_cellIndex;
#property(nonatomic, strong) MenuPageViewController *m_parentViewController;
-(IBAction) addToCart;
#end
MenuPAgeCell.m
#import "MenuPageCell.h"
#import "MenuPageViewController.h"
#implementation MenuPageCell
#synthesize m_cellIndex;
#synthesize m_parentViewController;
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
}
return self;
}
-(IBAction) addToCart
{
NSLog(#"Add To cart = %d",self.m_cellIndex);
[m_parentViewController addItemToCart:self.m_cellIndex];
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
MenuPageViewController.m
-(void) addItemToCart:(NSInteger)aIndexItem
{
NSLog(#"In Add to Cart method");
}
Now, This code works fine for non ARC Used project but its not working for me. I know it should be silly mistake but I'm unable to figure it out.
Thanks & regards
Mayur
Referencing ViewController from a cell is a design flaw, consider using delegate instead. But if you really need the ViewController property, make it weak instead of strong because currently you end up with retain cycle.
#protocol MenuPageCellDelegate<NSObject>
- (void)addItemToCart:(NSInteger)aIndexItem;
#end
#interface MenuPageCell : UITableViewCell {
NSInteger m_cellIndex;
}
#property(nonatomic, assign) NSInteger m_cellIndex;
#property(nonatomic, weak) id<MenuPageCellDelegate> delegate;
-(IBAction) addToCart;
#end
#implementation MenuPageCell
...
-(IBAction) addToCart
{
NSLog(#"Add To cart = %d",self.m_cellIndex);
if ([self.delegate responsToSelector:#selector(addItemToCart:)]) {
[self.delegate addItemToCart:self.m_cellIndex];
}
}
...
#end
Add MenuPageCellDelegate to the list of implemented protocols of MenuPageViewController and (if it's implementing UITableViewDataSource protocol) in the tableView:cellForRowAtIndexPath: method write cell.delegate = self; instead of cell.m_parentViewController = self;
initialize your m_parentViewController in viewDidLoad method.
such like
m_parentViewController = [[yourViewControllerName alloc] init];
and then call
[m_parentViewController addItemToCart:self.m_cellIndex];
in the cellforrow method of your tableview add a selector to the cell button and also set the tag equal to the indexpath. now in the selector just distinguish between the different cells with the help of sender.tag.

Why is my method not being called?

Why is my method not being called?
My UIViewController should be calling a method in my UIView called myMethod.
It only works on the inital UIView viewDidLoad.
After the view is loaded, I can't call the "myMethod" from someOtherMethod. And I don't understand why? XCode recognizes that the method exists and the method is exposed in my header.
MyViewController.h
#import “MyView.h”
#interface MyViewController : UIViewController {
MyView *mv;
}
MyViewContoller.m
#import “MyView.h”
- (void)viewDidLoad
{
mv = [[MyView alloc] initWithFrame:self.view.frame];
[self.view addSubview:mv];
//THIS WORKS IF CALLED FROM viewDidLoad
[mv myMethod];
}
- (void) someOtherMethod {
//THIS DOESN’T WORK IF CALLED LATER
[mv myMethod];
}
MyView.h
- (void) myMethod;
MyView.m
- (void) myMethod {
NSLog(#"My Method");
}

custom protocol doesn't work

In the app I'm working on, I have a UIViewController sublcass and a UIView subclass. in the storyboard the view controller contains the UIview. in the uiview I'm drawing something but I need it to know some values that it should be getting from the view controller. So I created a custom protocol in the view controller .h file:
#protocol SSGraphViewControllerProtocol <NSObject>
- (void)numberOfSemesters:(int)number;
#end
#property (weak, nonatomic) id <SSGraphViewControllerProtocol> delegate;
and in the UIView class I confirmed it as having the protocol above and I implemented its method. However. when I pass a number from the view controller, UIView doesn't receive it. Using NSLog, I figured out that UIView isn't entering - (void)numberOfS:(int)number; am I doing anything wrong? How can I fix it? and is there another way that I can send data from the UIViewController class to the UIView controller?
Here is the full code:
UIViewController.h
#protocol SSGraphViewControllerProtocol <NSObject>
- (void)numberOfSemesters:(int)number;
#end
#interface SSGraphViewController : UIViewController
#property (weak, nonatomic) id <SSGraphViewControllerProtocol> delegate;
#end
UIViewController.m
#implementation SSGraphViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[self.delegate numberOfSemesters:2];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
UIView.h
#interface SSGraph : UIView <SSGraphViewControllerProtocol>
#end
UIView.m
static int numberOfS = 0;
#implementation SSGraph
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
SSGraphViewController *graph = [[SSGraphViewController alloc] init];
graph.delegate = self;
return self;
}
- (void) numberOfSemesters:(int)number{NSLog(#"YES");
numberOfSemesters= number;
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
}
Read This Article, It is best example with Description
http://css.dzone.com/articles/do-not-publishcreating-your
Also read for create Protocol
Following i describe simple Example for How to create protocol
#DetailViewController.h
#import <UIKit/UIKit.h>
#protocol MasterDelegate <NSObject>
-(void) getButtonTitile:(NSString *)btnTitle;
#end
#interface DetailViewController : MasterViewController
#property (nonatomic, assign) id<MasterDelegate> customDelegate;
#DetailViewController.m
if([self.customDelegate respondsToSelector:#selector(getButtonTitile:)])
{
[self.customDelegate getButtonTitile:button.currentTitle];
}
#MasterViewController.m
create obj of DetailViewController
DetailViewController *obj = [[DetailViewController alloc] init];
obj.customDelegate = self;
[self.navigationController pushViewController:reportTypeVC animated:YES];
and add delegate method in MasterViewController.m for get button title.
#pragma mark -
#pragma mark - Custom Delegate Method
-(void) getButtonTitile:(NSString *)btnTitle;
{
NSLog(#"%#", btnTitle);
}
You're creating a view controller instance inside of initWithFrame:, assigning its delegate to be self, and then not keeping a reference to the controller or adding its view into the view hierarchy. This is certainly not what you meant to do. Make the connection in your storyboard instead, by making the delegate property an IBOutlet and connecting them by right clicking on the view controller and dragging from the circle next to the property name onto your view instance.
As an aside I'm not convinced of the utility of using a protocol in this way. If the view needs to know some information to do its job, if should either expose some properties that can be set by the controller, or declare a dataSource protocol and query its dataSource rather than rely on the view controller defining the interface it needs.
// Add an observer to your ViewController for some action in uiview
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receiveActionNotification:)
name:#"someActionNotification"
object:nil];
// Post Notification and method in your Viewcontroller will be called
[[NSNotificationCenter defaultCenter] postNotificationName:#"someActionNotification" object:self];
// at the end Dont forget to remove Observer.
[[NSNotificationCenter defaultCenter] removeObserver:#"someActionNotification"];

How do I set a nonzero initial value in iOS?

I have an ivar which is mentioned in my header
#interface MyClass : UIView{
int thistone;}
- (IBAction)toneButton:(UIButton *)sender;
#property int thistone;
#end
and I have synthesized it in the implementation:
#implementation MyClass
#synthesize thistone;
- (IBAction)toneButton:(UIButton *)sender {
if(thistone<4)
{thistone=1000;} // I hate this line.
else{thistone=thistone+1; }
}
I cannot find (or find in any manual) a way to set a nonzero initial value. I want it to start at 1000 and increase by 1 each time I press the button. The code does exactly what I intend, but I'm guessing there's a more proper way to do it that saves me the if/else statement above. Code fixes or pointers to specific lines in online documentation greatly appreciated.
Every object has a variant of the init method called at instantiation. Implement this method to do such setup. UIView in particular have initWithFrame: and initWithCoder. Best to override all and call a separate method to perform required setup.
For example:
- (void)commonSetup
{
thisTone = 1000;
}
- (id)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
[self commonSetup];
}
return self;
}
- (id)initWithCoder:(NSCoder *)coder
{
if (self = [super initWithCoder:coder])
{
[self commonSetup];
}
return self;
}

Solution for tracking state changes of UIButton

UIButton provides many state-dependent settings (image, titleColor, etc.).
I have manually added a subview to a button which shall react to the buttons state changes.
How would I do that? Should I try to map UIControlEvents on state changes?
You could do it by adding KVO observers for the button's selected and highlighted properties, but that's a lot more complicated than creating a subclass of UIButton and overloading the setSelected and setHighlighted methods. You'd do that like this:
//MyCustomButton.h
#interface MyCustomButton : UIButton
#end
//MyCustomButton.m
#implementation MyCustomButton
- (void)setUp
{
//add my subviews here
}
- (id)initWithFrame:(CGRect)frame
{
//this is called when you create your button in code
if ((self = [super initWithFrame:frame]))
{
[self setUp];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
//this is called when you create your button in interface builder
if ((self = [super initWithCoder:aDecoder]))
{
[self setUp];
}
return self;
}
- (void)setSelected:(BOOL)selected
{
super.selected = selected;
//update my subviews here
}
- (void)setHighlighted:(BOOL)highlighted
{
super.highlighted = highlighted;
//update my subviews here
}
#end
You can then create your custom buttons in code, or them in interface builder by dragging a regular UIButton onto your view and then settings its class to MyCustomButton in the inspector.

Resources