iOS: How to accomplish this delegate code with using block - ios

I use cocos2D for iphone.
I have 3 scenes: scene A,scene B, and scene C
in scene C, click to open scene A, but scene A is not opened yet;
open scene B first, scene B has many buttons
users click a button on scene B, then scene B dismisses and returns a value v to scene C;
according to value v, scene C decides to open or not open scene A;
All I need is a callback function, I use delegate to accomplish this successfully.
I have a protocol:
#protocol SceneBDelegate <NSObject>
#required
- (void)dismissWithValue:(BOOL)value;
#end
In scene B, when a button is clicked:
-(void) clickBtn{
if([self.delegate respondsToSelector:#selector(dismissWithValue:)]){
[self.delegate dismissWithValue: YES];
}
[self removeFromParent];
}
In scene C,
-(void) dismissWithValue:(BOOL)value{
if(value){
// do something;
}
}
These codes work well, but I want to know how to accomplish this with block?
I have readed about Jens Ayton's post is this question,
how-to-perform-callbacks-in-objective-c
He explains how to use block, but I can't figure out how to connect users' action with block.
I konw UIViewController has a
I found when using UIViewController has a function:
(void)dismissViewControllerAnimated: (BOOL)flag completion: (void (^)(void))completion NS_AVAILABLE_IOS(5_0);
it supports block.
But I use cocos2D, I didn't find a similar funciton.
Can anyone give me some advices? Thanks a lot!

Your scene B probably has an
#property (nonatomic, weak) id<SceneBDelegate> delegate;
instead you can store a block in a property:
#property (copy)void (^dismissWithValueBlock)(BOOL); // dismissWithValueBlock is the name of the variable.
Then in your scene B, when the button is pressed you do:
-(void) clickBtn{
if(self.dismissWithBlock){
self.dismissWithBlock(YES)
}
[self removeFromParent];
}
To create the block in your other class you should do this:
__weak typeof (self) weakSelf = self;
sceneB.dismissWithBlock = ^(BOOL value)
{
typeof (self) strongSelf = self;
// .... now your code you want to execute when this block is called..
// make sure not to call self.XXX anywhere in here. You should use the strongSelf.XXX insteadd! This is for memory management purposes. You can read up on it by googling 'strongSelf in blocks ios'
}

Yo can do that as bellow
//Define Block As in SceneBDelegate.h
typedef void (^onResultBlock)(NSDictionary *DictData, NSError *error);
- (void) onDismissWithValue:(onResultBlock) callbackBlock;
//Implementaion of Block As in SceneBDelegate.m
- (void) onDismissWithValue:(onResultBlock) callbackBlock
{
NSMutableDictionary *aMutDict = [[NSMutableDictionary alloc] init];
[aMutDict setValue:#"Value" forKey:#"Some Data"];
objCallback(aMutDict, nil);
}
//Call block from scene bellow
SceneBDelegate *aObjVC = [self.storyboard instantiateViewControllerWithIdentifier:#"SceneBDelegate"];
[aObjVC onDismissWithValue:^(NSDictionary *DictData, NSError *error)
{
}];

Try using this code:
#interface YourClass : NSObject {
void (^_block)(BOOL *result);
}
-(void) yourMethod:(NSString *)param1 withAnotherParam:(NSDictionary *)param2 withBlock:(void(^)(BOOL *result))block;
#implementation YourClass
-(void) yourMethod:(NSString *)param1 withAnotherParam:(NSDictionary *)param2 withBlock:(void(^)(BOOL *result))block
{
block = [block copy];
//do some stuff
block(YES);
}
And then, when you call yourMethod from a different class, you can do:
#implementation OtherClass
-(void) otherMethod
{
//do other stuff
[yourClassInstance yourMethod:#"hello" withAnotherParam:#{#"hello": #"hello"} withBlock:(void(^)(BOOL *result)){
//do stuff in the block....
}];
}
I hope this helps.

Related

How to call block defined as property in another class?

I am calling the block from second class which has been declared and maintained in first class.
In ViewController.h
#property (copy) void (^simpleBlock)(NSString*);
In View Controller.m
- (void)viewDidLoad {
[super viewDidLoad];
self.simpleBlock = ^(NSString *str)
{
NSLog(#"Hello My Name is: %#",str);
};
}
In SecondViewController.m
In ViewDidload
ViewController *VC = [[ViewController alloc]init];
VC.simpleBlock(#"Harjot");//bad execution error
Please suggest me some solutions because the code is giving me bad execution error.
How can i call the block in any another way?
It's the correct way of run the block. However if you try to run a block that is nil you'll have a crash - so you should always check that it's not nil before calling it:
ViewController *vc = [[ViewController alloc] init];
if (vc.simpleClock) {
vc.simpleBlock(#"Harjot");//this will not get called
}
The reason why in your case the block is nil is because you set it in viewDidLoad - however viewDidLoad is not called until its view is ready to go on screen. For testing purposes try to move the assignment from viewDidLoad to init and this should work:
- (instancetype)init
{
self [super init];
if (self) {
_simpleBlock = ^(NSString *str)
{
NSLog(#"Hello My Name is: %#",str);
};
}
return self;
}

Is it bad design to set self.delegate = self

I have a UIViewController subclass (say MyViewController).
MyViewController.h
#protocol TargetChangedDelegate
-(void) targetChanged;
#end
#interface MyViewController
#property (weak) id<TargetChangedDelegate> targetChangedDelegate;
-(void) doSomethingOnYourOwn;
#end
MyViewController.m
#implementation MyViewController <TargetChangedDelegate>
-(void) doSomethingOnYourOwn
{
// DO some stuff here
// IS THIS BAD ??
self.targetChangedDelegate = self;
}
-(IBAction) targetSelectionChanged
{
[self.targetChangedDelegate targetChanged];
}
-(void) targetChanged
{
// Do some stuff here
}
#end
Based on certain conditions a class that instantiates an instance of MyViewController may decide to set itself as the delegate or not.
Foo.m
#property(strong) MyViewController *myVC;
-(void) configureViews
{
self.myVC = [[MyViewController alloc] init];
[self.view addSubview:self.myVC];
if (someCondition)
{
self.myVC.targetChangedDelegate = self;
}
else
{
[self.myVC doSomethingOnYourOwn]
//MyViewController sets itself as the targetChangedDelegate
}
}
With reference to the code snippet above, I have the following question:
Is it a violation of MVC/delegation design pattern (or just a bad design) to say:
self.delegate = self;
There's absolutely no problem with setting the delegate to self. In fact it is a good way to provide default delegate functionality if a delegate is not set by somebody else.
Obviously, the delegate property has to be declared weak otherwise you get a reference cycle.
To expand a bit, having read the wrong answer and wrong comments above, if you allow an object to be its own delegate, your code is cleaner because you do not have to surround absolutely every single delegate call with
if ([self delegate] != nil)
{
[[self delegate] someMethod];
}
else
{
[self someMethod];
}
Its not proper way to assign self.delegate = self.
for your functionality, you can do this:
-(void) doSomethingOnYourOwn
{
// DO some stuff here
self.targetChangedDelegate = nil;
}
and when using delegate:
if(self.targetChangedDelegate != nil && [self.targetChangedDelegate respondsToSelector:#selector(targetChanged)]
{
[self.targetChangedDelegate targetChanged];
}
else
{
[self targetChanged];
}
It is bad design to set self.delegate = self; it should be another object. Delegation via protocols are an alternative design to subclassing and you can read more about delegation here:
https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/Delegation.html
And here is more on protocols:
https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/Protocol.html

iOS : How to make a simple block?

I've a custom view subclass with UIView, in it, I've two buttons with an action, now I want to learn something good, and I don't want to implement a delegate to know which button tapped from other class.
I'm showing my custom view with this method,
CustomView *view = [[CustomView alloc] init];
[view show];
This will show my custom view, with two buttons inside (please assume everything is working fine, I only want to implement block to know which button have been tapped). ^_^
What I've tried for block,
- (void) showViewWithCompletionBlock:(void(^)(CustomViewType type))completion;
and yes, I would able to write like this,
CustomView *view = [[CustomView alloc] init];
[view showViewWithCompletionBlock:^(CustomViewType type) {
}];
but now here's the trouble, I don't know how to call this block (or how I can return CustomViewType) when button tap?
Those two buttons action is like this,
- (void) someAction:(UIButton *)sender {
//sender.tag is a CustomViewType which user choose
}
For a note, CustomViewType is an enum like this,
typedef enum {
CustomViewTypeOption1,
CustomViewTypeOption2,
}CustomViewType;
It's easier to deal with blocks using a typedef:
typedef void(^CustomViewCompletionBlock)(CustomViewType type);
(same thing goes for function pointers).
Now store this block in the custom view, using a private category in the .m file:
#interface CustomView () {
CustomViewCompletionBlock _completionBlock;
}
#end
#implementation CustomView
...
#end
Do whatever is necessary to display the view and store the completion block:
- (void)showViewWithCompletionBlock:(CustomViewCompletionBlock)completion
{
// Do whatever it takes to "display" the view
...
_completionBlock = completion;
}
and then call the completion block as-and-when:
- (IBAction)button1Action:(id)sender
{
// Whatever else this method does
...
if (_completionBlock) {
_completionBlock(CustomViewTypeOption1);
}
}
- (IBAction)button2Action:(id)sender
{
// Whatever else this method does
...
if (_completionBlock) {
_completionBlock(CustomViewTypeOption2);
}
}
Please try below code. when you call compilation . take reference of that compilation and when you want to call that block just use that reference.
in .h
typedef enum {
CustomViewTypeOption1,
CustomViewTypeOption2,
}CustomViewType;
typedef void(^CustomViewCompletionBlock)(CustomViewType type);
#interface CustomView : UIView
{
CustomViewCompletionBlock custVTypeBlock;
}
- (void)showViewWithCompletionBlock:(CustomViewCompletionBlock)completion;
#end
in .m
- (void)showViewWithCompletionBlock:(CustomViewCompletionBlock)completion
{
custVTypeBlock = completion;
//i'm calling this for sample.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self someAction:nil];
});
}
- (void) someAction:(UIButton *)sender {
//sender.tag is a CustomViewType which user choose
// call your compilation with your enum.
if(custVTypeBlock)
custVTypeBlock(0);
}
Maybe this will help you.
Try https://github.com/lavoy/ALActionBlocks
It supports UIControl (UIButton), UIBarButtonItem, and UIGestureRecognizer. It is also supported using CocoaPods.
Calling a block type is just like calling a C method. If you your block variable name is, say, myBlock and if it returns a CustomType, then this will work for you:
CustomType blockReturnedCustomType = myBlock();

Execute function to call performSegueWithIdentifier from another class

I've spent a few hours on this trying to work it out myself but I give up!
I have a master-detail arrangement where the user input screen needs to call a function on another class to post to a web service. Upon completion of the asynchronous call, the class will then call a specified function. In this case, I'm just testing and all I want to do is go back to the main screen after the user input is accepted by the web service.
When the uses taps a button on the input screen (SetLocationViewController), the asynchronous operation is called in the class APIPostClass. After it is complete, I want SetLocationViewController to segue back to MasterViewController.
In APIPostClass.m in (called after the asynchronous op finishes)
-(void)callWhenDone {
NSLog(#"callWhenDone loaded.");
SetLocationViewController *SLVClassInstance = [[SetLocationViewController alloc] init];
[SLVClassInstance doSegue];
}
In SetLocationViewController.m
-(void) doSegue {
NSLog(#"doSegue loaded");
[self performSegueWithIdentifier:#"SetLocationViewControllerManualUnwind" sender:self];
}
Calling doSegue from an action on SetLocationViewController.m does work so I know my segue is ok but the above doesn't work. I get the error reason: 'Receiver () has no segue with identifier 'SetLocationViewControllerManualUnwind''
I'm guessing the reason is because of the alloc init way of initialising of the VC, but I don't know any better. Thus, how can I call a function on another class as if it was being called by it's own class?
Create a delegate it would be much more reliable and fast than Notifications.
#protocol APIPostDelegate <NSObject>
#required
-(void)OnRequestSucess;
#end
In your APIPost add new property for delegate
#interface APIPost : NSObject
#property (weak) id<APIPostDelegate> delegate;
In SetLocationViewController implement APIPostDelegate
SetLocationViewController.h
SetLocationViewController :NSObject<APIPostDelegate>
SetLocationViewController.m
-(void)OnRequestSucess
{
[self doSegue];
}
before you make call to method on APIPost, assign self to delegate property.
APIPost *apipost=[[APIPost alloc]init];
apipost.delegate=self;
[apipost <your api method>];
APIPost.m
[self.delegate OnRequestSucess];
Hope this helps.
There are a few methods to make it happens:-
Use Delegate
Use NSNotification.
The way described by Artur above (For SplitViewController Only - iPad)
You should use delegate whenever it is possible but it might not be too straight forward. NSNotification is more straight forward but it is not a good practice and not a good programming style.
I will only share the NSNotification method as it is easier to implement.
In SetLocationViewController.m
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(doSegue) name:#"calldoSegue" object:nil];
}
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter]removeObserver:self name:#"calldoSegue" object:nil];
}
-(void) doSegue {
NSLog(#"doSegue loaded");
[self performSegueWithIdentifier:#"SetLocationViewControllerManualUnwind" sender:self];
}
In APIPostClass.m
-(void)callWhenDone {
NSLog(#"callWhenDone loaded.");
[[NSNotificationCenter defaultCenter]postNotificationName:#"calldoSegue" object:nil];
}
The above code should work but again, this is not a good practice. You should try to learn the Delegate method.
The answer is here: Performing segue from another class
In my APIPostClass.h, I setup the view controller:
#interface APIPostClass : NSObject {
SetLocationViewController *setLocationViewController;
}
#property(nonatomic, strong) SetLocationViewController *setLocationViewController;
#end
In my APIPostClass.m, I synthesize it:
#synthesize setLocationViewController;
then, instead of this (as in my question):
-(void)callWhenDone {
NSLog(#"callWhenDone loaded.");
SetLocationViewController *SLVClassInstance = [[SetLocationViewController alloc] init];
[SLVClassInstance doSegue];
}
I have:
-(void)callWhenDone {
NSLog(#"callWhenDone loaded");
[self.setLocationViewController doSegue];
}
Over in SetLocationViewController.m, the segue method remains unchanged:
-(void) doSegue {
NSLog(#"doSegue loaded");
[self performSegueWithIdentifier:#"SetLocationViewControllerManualUnwind" sender:self];
}
But when I call my API, I need to "attach" (forgive my terminology) the view controller to it. This is what I had:
- (IBAction)btnTestAPICall:(id)sender {
NSLog(#"User tapped API button");
APIPostClass *APIPostClassInstance = [[APIPostClass alloc] init];
[APIPostClassInstance APICall: ... ....
}
But this is what works after bringing all of the above:
- (IBAction)btnTestAPICall:(id)sender {
NSLog(#"User tapped API button");
APIPostClass *APIPostClassInstance= [[APIPostClass alloc] init];
UIViewController *currentVC=self;
APIPostClassInstance.setLocationViewController = currentVC;
[APIPostClassInstance APICall: ... ...
I hope this will help someone else!

Warning when dismissing view controller programatically

I have a TableViewController with a button that triggers an [NSURLConnection sendAsynchronousRequest...] event, and also loads a modal segue through performSegueWithIdentifier:sender: which targets a small view controller. The purpose of this overlay view controller is to show a loading graphic and to prevent user interaction while the data is sent through the NSURLConnection.
In the completion block of the NSURLConnection, I call a method which removes the data in the TableViewController (its just a batch listing), and then calls dismissViewControllerAnimated:completion: on the overlay view controller.
Everything works except for dismissing the overlay view controller, which throws a warning in the debugger which says:
"Warning: Attempt to dismiss from view controller while a presentation or dismiss is in progress!"
I have found various questions and answers about this error, particularly about using the performSelector:object:withDelay methods, but so far nothing has worked.
This is particularly annoying because I use a similar process in another area of the app, except that the dismissViewController is called from selecting a UITableViewCell, and this works fine...
The relevant bits of my code are shown below:
#import "ViewBatch.h"
#interface ViewBatch ()
#property (strong, nonatomic) LoadingOverlayViewController *loadingOverlay;
#end
#implementation ViewBatch
#synthesize loadingOverlay;
....
- (IBAction)exportBatch:(id)sender
{
if ([productArray count] > 0) {
[self performSegueWithIdentifier:#"loadingSegue" sender:self];
[self processData];
}
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"loadingSegue"]) {
loadingOverlay = segue.destinationViewController;
}
}
- (void)processData
{
// Code to create a file and NSURLRequest...
// ....
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:urlRequest
queue:queue
completionHandler:^(NSURLResponse *response, NSData *responseData, NSError *error) {
if ([responseData length] > 0 && error == nil)
{
// Not used for this request yet...
}
else if ([responseData length] == 0 && error == nil)
{
// Success...
[self didSendData];
}
else if (error != nil)
{
// Connection error...
NSLog(#"Error: %#", error);
}
}];
}
- (void)didSendData
{
// Reset the batch...
[productArray removeAllObjects];
[self.tableView reloadData];
[loadingOverlay dismissViewControllerAnimated:YES completion:NULL];
}
And the loading view controller is just:
#import <UIKit/UIKit.h>
#interface LoadingOverlayViewController : UIViewController
#property (weak, nonatomic) IBOutlet UILabel *statusLabel;
#property (weak, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicator;
#end
....
....
#import "LoadingOverlayViewController.h"
#interface LoadingOverlayViewController ()
#end
#implementation LoadingOverlayViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self.activityIndicator startAnimating];
}
#end
I'm not sure about the exact cause of the problem, but here are some observations about your code.
You do not need an overlay in order to prevent user interaction. Simply turn off user interaction with -NSApplication beginIgnoringInteractionEvents.
You do not need an entire view controller in order to show an overlay view. Just show the view. For example I often place a large UIActivityIndicatorView in the middle of my view while an NSURLConnection is happening.
You do not need an NSOperationQueue in order to use NSURLConnection asynchronously. It is already asynchronous. Just create the NSURLConnection and wait for the delegate messages to arrive. As it is, you are bolluxing yourself because you are setting up a secondary queue, the message arrives on that queue, and you call didSendData which calls reloadData on the table - in the background, which is illegal. If you did this the normal way, your delegate messages would arrive on the main thread which is exactly what you want.
Never mind, I got it worked out.
Amazing what half an hours break can do to organise one's thoughts...
The process was finishing and calling dismissViewController before the actual view was even finished loading. A simple delegate call from the viewDidLoad on the overlay sorted things out.
I had this problem as well. I noticed it was a result from copying and pasting a button in my nib file. The result of this was that I had to IBActions tied two 1 UIButton.

Resources