replace delegate using block to pass value - iphone

I have read so many positive things about using blocks - in particular that it simplifys the code by elimanting delegate calls. I have found examples where blocks are used at end of animation instead of delegate calls. the block example is easy but i cannot use the example to the iphone app.for example i use the delegate:
.h
#protocol AWActionSheetDelegate <NSObject>
- (int)numberOfItemsInActionSheet;
- (AWActionSheetCell*)cellForActionAtIndex:(NSInteger)index;
- (void)DidTapOnItemAtIndex:(NSInteger)index;
#end
#interface AWActionSheet : UIActionSheet
#property (nonatomic, assign)id<AWActionSheetDelegate> IconDelegate;
-(id)initwithIconSheetDelegate:(id<AWActionSheetDelegate>)delegate ItemCount:(int)cout;
#end
.m
- (void)actionForItem:(UITapGestureRecognizer*)recongizer
{
[IconDelegate DidTapOnItemAtIndex:cell.index];
}
and i use it :
-(void)DidTapOnItemAtIndex:(NSInteger)index
{
NSLog(#"tap on %d",index);
}
how use block not use delegate i can get the index,can you give advice and if give good block category to finish the effect is very good . i donot want to use delegate to pass the index,i only want to use block to get the index

I think you are looking for something like this:
//implementation for AWActionSheet's method: actionForItem:withBlock:
-(void) actionForItem:(UITapGestureRecognizer*)recongizer withBlock:(void(^)(NSInteger integer)) block {
NSInteger myInt = 0;
//whatever
//callback
block(myInt);
}
and the call
AWActionSheet* actionSheet;
[actionsheet actionForItem:recognizer withBlock:^(NSInteger integer) {
NSLog(#"myInt: %d", integer);
}];

Use this object:
https://github.com/matteogobbi/MGActionSheet
This is an easy example to init and use the object by block:
//Initialization
MGActionSheet *actionSheet = [[MGActionSheet alloc] initWithTitle:#"Block action sheet!" cancelButtonTitle:#"Cancel" destructiveButtonTitle:#"Delete" otherButtonTitles:#"Option 1", #"Option 2", #"Option 3", nil];
//Show with completition block
[actionSheet showInView:self.view withChoiceCompletition:^(int buttonIndex) {
if(buttonIndex == actionSheet.cancelButtonIndex) NSLog(#"Cancelled");
else if(buttonIndex == actionSheet.destructiveButtonIndex) NSLog(#"Destructed");
else {
NSLog(#"Option at index: %d", buttonIndex);
}
}];

Related

How to use Protocols objective-c

I need to inherit Picker selected values into the other place .I am trying the below code but null value is coming ..please check where I am going wrong.
I have to Inherit String values that is been passes in the PickerView..please check the code
Picker1.h
#import <UIKit/UIKit.h>
#protocol pickerDelegate <NSObject>
-(void)didFinishPicking:(NSString *)pickedStr;
#end
#interface
#property(nonatomic,retain)id<pickerDelegate>delegate;
picker.m
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
string=[NSString stringWithFormat:#"%#",[list objectAtIndex:row]];
label.text=string;
[self.delegate didFinishPicking:string];
}
- (void)viewDidLoad
{
[super viewDidLoad];
list =[[NSMutableArray alloc]init];
[list addObject:#"a"];
[list addObject:#"b"];
}
Acitivity_1.h
#import <UIKit/UIKit.h>
#import "Picker1.h"
#interface Activity_1 : UIViewController<UIApplicationDelegate, pickerDelegate>{
#property(nonatomic,retain)Picker1 *delegate1;
#property (nonatomic,retain)NSString *str;
#end
Activity_1.m
- (void)viewDidLoad
{
[super viewDidLoad];
**this is how i print the value but value is null**
NSLog(#"delegate1%#",self.delegate1.string);
delegate1 = [[Picker1 alloc] init];
[delegate1 setDelegate : self];
}
-(void)didFinishPicking:(NSString *)pickedStr
{
[self setStr:pickedStr];
}
You are printing out a value of a delegate just before you are setting it up....so it will print null. You should print out your string when the didFinishPicking method is called instead since this is where you are setting up your string.
-(void)didFinishPicking:(NSString *)pickedStr
{
[self setStr:pickedStr];
// print the string you have just picked here if you want
NSLog(#"Picked string: %#",pickedStr);
}
Note one the side: avoid any name convention with number such as Activity_1, Picker1 this is extremely bad code practice.
You are NSLogging delegate before creating self.delegate1 itself
Please use the below lines of code.
delegate1 = [[Picker1 alloc] init];
[delegate1 setDelegate : self];
And put NSLog inside "didFinishPicking"
-(void)didFinishPicking:(NSString *)pickedStr
{
NSLog(#"pickedStr%#", pickedStr);
[self setStr:pickedStr];
}

call method from subclass of UIImageview

I have two .m files. The first is the main code, The second is a subclass of UIImageView so that i can detect touches.
In the main .m file I have added a progress bar and a customimageview both subviews of a scrollview.
What I need is that when a user touches the customimageview that the progress bar moves up and a double tap decreases the [Note: the customimageview has to have its touches recognised in the second .m because of them being in a subview of a scrollview and other controls are having to be handled]
In the main .m file I have a two methods:
- (void)pumpsingletap {
progbm.progress +=0.1;
}
- (void)pumpdoubletap {
progbm.progress -=0.1;
}
then in the subclassed uiimageview i have:
//inside touches method
if ([touch view].tag == 555) {
NSLog(#"pump touched");
switch ([allTouches count]) {
case 1: {
switch ([touch tapCount]) {
//---single tap---
case 1: {
NSLog(#"single pump touch");
[self performSelector:#selector(pumpsingletap) withObject:nil afterDelay:.4];
} break;
//---double tap---
case 2: {
NSLog(#"double pump touch");
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(pumpsingletap) object:nil];
[self performSelector:#selector(pumpdoubletap) withObject:nil afterDelay:.4];
} break;
}
}
}
}
So the NSlog's appear so the touch recognition isn't an issue. But the performSelector falls over. As the customimageview pumpsingletap doesnt work.
So how do i call the method in the subclass.
//update//
so I have added in the following code, in my subclass
mainMethod* callingMethod = [[mainMethod alloc] init];
[callingMethod performSelector:#selector(pumpsingletap) withObject:nil afterDelay:.4];
then in my main method for pumpsingletap i changed it to:
- (void)pumpsingletap {
NSLog(#"single pump method called");
progbm.progress +=0.1;
}
The NSLog for single pump method called appeared but the progress bar progbm - didn't move. so i have solved my calling issue - just need to now work out why the progress bar isnt moving!!
If I'm following the code correctly, you are performing the selector on self, but self is your derived UIImageView class (so you probably crash at that point?). You need to perform selector on the class in your main file. Pass a reference to that to your derived class. Alternately, you could create a delegate, implement it in your main class, pass your main class to the UIImageView and then call through the delegate (there are even other ways to do it (key-value observation), but one of these should work).
I'm not sure if this is the problem, but you don't need brackets around case statements. Also I don't get why you would make a switch within a switch. Just make an if-elseif-else statement, it'll probably be easier to understand.
Other than this, from what I understand, you have a view controller with both a progress bar and a customimageview as properties, and you have methods that should be called in response to certain actions, (tapping or double tapping the customimageview) but they're in the view controller. The usual way to solve this is by using the target action mechanism. UIControls implement the target action mechanism by encapsulating target-action pairs and storing them in a dictionary, keyed by the event type (UIControlEvent). Here's a slightly simpler version.
In the .h file for your subclass of UIImageView, before the #interface write this:
typedef enum {
ControlEventTap = 0,
ControlEventDoubleTap
} ControlEvent;
Then in the .m file add this before the #implementation:
#interface TargetActionPair : NSObject {
id target;
SEL action;
}
#property (nonatomic, assign) id target;
#property (nonatomic, assign) SEL action;
#end
#implementation TargetActionPair
#synthesize target, action;
#end
Then, add an NSMutableArray instance variable, (but not a property) and a - (void)setTarget:(id)t action:(SEL)a forEvent:(ControlEvent)e method to your customimageview implementation.
The method should look like this:
- (void)setTarget:(id)t action:(SEL)a forEvent:(ControlEvent)e {
TargetActionPair *tar_act = [[TargetActionPair alloc] init];
tar_act.target = t;
tar_act.action = a;
// actionsArray is the mutable array instance variable and must be allocated and set in the init method for customimageview.
[actionsArray replaceObjectAtIndex:(NSUInteger)e withObject:tar_act];
[tar_act release];
}
Then you can replace your touch handling code with:
if ([touch view].tag == 555) {
NSUInteger tapcount = [touch tapCount];
if (([alltouches count] == 1) && (tapcount <= [actionsArray count])) {
TargetActionPair *tar_act = [actionsArray objectAtIndex:tapcount-1];
[tar_act.target performSelector:tar_act.action withObject:nil afterDelay:.4];
if (tapcount == 2) {
TargetActionPair *tar_act2 = [actionsArray objectAtIndex:tapcount-2];
[NSObject cancelPreviousPerformRequestsWithTarget:tar_act2.target selector:tar_act2.action object:nil];
}
}
}
With this code you simply set the target and action for each control event in the viewDidLoad method of the view controller that contains the customimageview. So the calls would look like this:
[self.customimageview setTarget:self action:#selector(pumpsingletap) forEvent:ControlEventTap];
[self.customimageview setTarget:self action:#selector(pumpdoubletap) forEvent:ControlEventDoubleTap];
DON'T FORGET to release the actionsArray in your dealloc method and to be very careful about releasing the view controller since the customimageview doesn't retain it.
I hope this helps, best of luck on your app.
In the end I solved the issue by using NSNotificationCenter

iPhone, error: object cannot be set - either readonly property or no setter found

I know there's another question like this on stack overflow, but my problem is from my own class not a UIButton. My method is a lot more complicated. The first commented line works.
#implementation GettingStartedViewController
- (void)actionSheet:(UIActionSheet *)actionSheet
clickedButtonAtIndex:(NSInteger)buttonIndex {
//[cui showHelpClickButtonAtIndex:buttonIndex:self.view:
//theController:FALSE:HelpPageGettingStarted];
// Error from this next line error: object cannot be set - either
//readonly property or no setter found
self.theController = [cui showHelpClickButtonAtIndex:buttonIndex:
self.view:theController:FALSE:HelpPageGettingStarted];
}
- (void)viewDidLoad {
[super viewDidLoad];
cui = [CommonUI alloc]; // Used for several functions
}
And heres the function it calls. I've trying to reuse code and keep things in one function. the function uses addSubView and presentModalViewController.
#implementation CommonUI
-(UIViewController *)showHelpClickButtonAtIndex:(int)buttonIndex:
(UIView *)vw:(UIViewController *)vc:(BOOL)useNav:(HelpPage)page{
if (buttonIndex == CommonUIInfoHelpPagesBtnIdx) {
if (useNav == TRUE) {
// snip
} else {
vc = [[HelpViewController alloc] initWithNibName:#"HelpView"
bundle:nil onPage:page];
[vw addSubview:vc.view];
return [vc autorelease];
}
} else {
// Snip
}
// Snip
}
The message says that there is no property for theController in GettingStartedViewController. Your method call works just fine.
self.theController = someObject is the same as calling the setter method: [self setTheController:someObject]
Properties automatically generate those getters and setters; so if you did not define a property, it is not going to create a setter and this is your problem here.
Add the following to your header file:
#property (nonatomic, retain) UIViewController* theController;
And synthesize it in your implementation file:
#synthesize theController;
Do not forget to release it in the -dealloc method, as you told the setter to retain the object:
-(void) dealloc {
[theController release];
theController = nil;
}
iPhone, error: object cannot be set - either readonly property or no setter found error comes when you have not synthesized it and still accessing its setter or/and getter method.

Newbie question: NSOperation for iphone SDK

Hi I got some problem with NSOperation .
I always got error at self = [super init]; (already use break point to find this)
it always return "Program received signal: EXC_BAD_ACCESS" all the time
//AddThread.h
#interface AddThread : NSOperation
{
NSString * str;
}
#property (nonatomic,retain) NSString * str;
-(id) initWithString:(NSString *) tmpStr;
#end
and for .m
//AddThread.m
#import "AddThread.h"
#implementation AddThread
#synthesize str;
- (id) initWithString:(NSString *)tmpStr
{
self = [super init];
if (self != nil)
{
self.str = tmpStr;
}
//NSLog(self);
//[super init];
return self;
}
- (void) main
{
NSLog(self.str);
}
- (void) dealloc{
[str release];
str = nil;
[super dealloc];
}
#end
well I stuck with this for while and if possible any resources ,articles things for basic example of NSoperation?
In your main method, you are calling NSLog(self.str) - While this will work if the object you pass in is a string, it won't work if you continue to try and log other objects. NSLog takes a format string as a parameter. If you just do NSLog(self) like you are in some of your commented code, and self is not a string, it'll crash because it expected a string. You should do NSLog(#"self: %#", self) the %# will print out the string returned by an objects description method.
Other than that, your init method looks fine, how exactly are you creating an instance of this object? Could you show the code for that? The problem may lie there.

How to safely pass a context object in an UIAlertView delegate?

In my application I use a UIAlertView to display to the user a message and some options. Depending on the button pressed, I want the application to perform something on an object.
The sample code I use is...
-(void) showAlert: (id) ctx {
UIAlertView *baseAlert = [[UIAlertView alloc]
initWithTitle: title
message: msg
delegate:self
cancelButtonTitle: cancelButtonTitle
otherButtonTitles: buttonTitle1, buttonTitle2, nil];
//baseAlert.context = ctx;
[baseAlert show];
[baseAlert release];
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
if (buttonIndex == 1) {
id context = ...;//alertView.context;
[self performSelectorOnMainThread:#selector(xxx:) withObject: context waitUntilDone: NO];
}
}
Is there any way to pass an object into the delegate as a context object? or maybe some other way?
I could add the property on the delegate but the same delegate object is being used by many different alert views. For this reason I would prefer a solution where the context object is attached to the UIAlertView instance and carried across to the delegate as part of the UIAlertView object.
I still think storing it locally is the best solution. Create a class local NSMutableDictionary variable to hold a map of context objects, store the context with UIAlertView as the key and the context as the value.
Then when the alert method is called just look into the dictionary to see which context object is related. If you don't want to use the whole Alert object as a key, you could use just the address of the UIAlertView object:
NSString *alertKey = [NSString stringWithFormat:#"%x", baseAlert];
The address should be constant on the phone. Or you could tag each alert as the other poster suggested and use the tag to look up a context in the map.
Don't forget to clear out the context object when you are done!
A complete implementation that allows you to pass context:
#interface TDAlertView : UIAlertView
#property (nonatomic, strong) id context;
#end
#implementation TDAlertView
#end
And a usage example, note how we pre-cast the pointer:
#implementation SomeAlertViewDelegate
- (void)alertView:(TDAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
NSLog(#"%#", [alertView context]);
}
#end
you can also use the tag property (since it's a UIView subclass). This is just an int, but may be enough for you.
Instead of debating the meaning of "does not support subclassing", I'll provide a better answer. I created a generic contextInfo category for my job a couple months ago. I just put it on github: JLTContextInfo.
#import "objc/runtime.h"
#interface NSObject (JLTContextInfo)
- (NSMutableDictionary *)JLT_contextInfo;
#end
#implementation NSObject (JLTContextInfo)
- (NSMutableDictionary *)JLT_contextInfo
{
static char key;
NSMutableDictionary *contextInfo = objc_getAssociatedObject(self, &key);
if (!contextInfo) {
contextInfo = [NSMutableDictionary dictionary];
objc_setAssociatedObject(self, &key, contextInfo, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return contextInfo;
}
#end
This creates a place to easily store extra data for any object derived from NSObject. Now the answer is nearly identical to the original question.
-(void) showAlert: (id) ctx {
UIAlertView *baseAlert = [[UIAlertView alloc]
initWithTitle: title
message: msg
delegate:self
cancelButtonTitle: cancelButtonTitle
otherButtonTitles: buttonTitle1, buttonTitle2, nil];
[[baseAlert JLT_contextInfo] setObject:ctx forKey:#"ctx"];
[baseAlert show];
[baseAlert release];
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
if (buttonIndex == 1) {
id context = [[alertView JLT_contextInfo] objectForKey:#"ctx"];
[self performSelectorOnMainThread:#selector(xxx:) withObject: context waitUntilDone: NO];
}
}
Subclassing UIAlertView is not a good idea.
http://developer.apple.com/library/ios/#DOCUMENTATION/UIKit/Reference/UIAlertView_Class/UIAlertView/UIAlertView.html
Subclassing Notes
The UIAlertView class is intended to be used as-is and does not support subclassing. The view hierarchy for this class is private and must not be modified.
UIAlertView with a user supplied context and [self autorelease] answers this question in a different way.
I did a mix between Kendall's answer and the uses of blocks in one of my base view controller classes. Now I can use AlertView and ActionSheets with blocks which improves greatly readability. Here is how I did it :
In the .h of my ViewController I declare a block type (optional but recommanded)
typedef void (^AlertViewBlock)(UIAlertView*,NSInteger);
Also I declare a mutable dictionnary that will store the blocks for each alertview :
NSMutableDictionary* m_AlertViewContext;
In the implementation file I add a method to create the AlertView and save the block :
-(void)displayAlertViewWithTitle:(NSString *)title message:(NSString *)message cancelButtonTitle:(NSString *)cancelButtonTitle withBlock:(AlertViewBlock)execBlock otherButtonTitles:(NSArray *)otherButtonTitles
{
UIAlertView* alert = [[UIAlertView alloc] initWithTitle:title
message:message
delegate:self cancelButtonTitle:cancelButtonTitle otherButtonTitles: nil];
for (NSString* otherButtonTitle in otherButtonTitles) {
[alert addButtonWithTitle:otherButtonTitle];
}
AlertViewBlock blockCopy = Block_copy(execBlock);
[m_AlertViewContext setObject:blockCopy forKey:[NSString stringWithFormat:#"%p",alert]];
Block_release(blockCopy);
[alert show];
[alert release];
}
Note that I receive the same attributes as the constructor of the UIAlertView but the delegate (which will be self). Also I receive a AlertViewBlock object that I save in the m_AlertViewContext mutable dictionnary.
Then I show the alert as I would usually do.
In the delegate callbacks, I call the block and give it the parameters :
#pragma mark -
#pragma mark UIAlertViewDelegate
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
NSString* blockKey = [NSString stringWithFormat:#"%p",alertView];
AlertViewBlock block = [m_AlertViewContext objectForKey:blockKey];
block(alertView,buttonIndex);
[m_AlertViewContext removeObjectForKey:blockKey];
}
- (void)alertViewCancel:(UIAlertView *)alertView {
NSString* blockKey = [NSString stringWithFormat:#"%p",alertView];
[m_AlertViewContext removeObjectForKey:blockKey];
}
Now, whenever I need to use an AlertView I can call it like this :
[self displayAlertViewWithTitle:#"Title"
message:#"msg"
cancelButtonTitle:#"Cancel"
withBlock:^(UIAlertView *alertView, NSInteger buttonIndex) {
if ([[alertView buttonTitleAtIndex:buttonIndex] isEqualToString:#"DO ACTION"]){
[self doWhatYouHaveToDo];
}
} otherButtonTitles:[NSArray arrayWithObject:#"DO ACTION"]];
I did the same for the ActionSheet and now it's really easy to use those.
Hope it helps.
From my other answer, here's a quick and clean solution that takes advantage of associated objects. I mention in my other answer that you could even replace UIAlertView with NSObject and effectively add a context property to any object:
#import <objc/runtime.h>
#interface UIAlertView (Private)
#property (nonatomic, strong) id context;
#end
#implementation UIAlertView (Private)
#dynamic context;
-(void)setContext:(id)context {
objc_setAssociatedObject(self, #selector(context), context, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(id)context {
return objc_getAssociatedObject(self, #selector(context));
}
#end
And then you'll be able to do something like:
NSObject *myObject = [NSObject new];
UIAlertView *alertView = ...
alertView.context = myObject;
IMPORTANT:
And don't forget to nil the context in dealloc!!
You could subclass UIAlertView and add the property there.