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();
Related
I have no idea how I should correctly name the title but I know exactly what my problem is (I will eventually edit the title later).
I am pretty new to Objective-C and I am still learning.
So, I have a class that contains a tableView (I will call it ClassA) and another with a normal UIView (ClassB). What I want to do, is to let a button appear when a row is selected.
I created in my ClassB.h file:
+(id)sharedInstance;
#property (retain, nonatomic) IBOutlet UIButton *btn;
-(void) showBtn :(BOOL) show;
And in my ClassB.m file:
#synthesize btn;
static ClassB *this = nil;
(+id) sharedInstance {
if(!this) {
#synchronized (self) {
this = [[ClassB alloc] init];
}
}
return this;
}
-(void)viewDidLoad {
[self showBtn:NO] //because I only want to let it appear when a row is selected.
[self.view addSubview:btn];
}
-(void) showBtn :(BOOL) show { // I called this method in classA.
if (show == NO) {
btn.hidden = YES;
} else {
btn.hidden = NO;
}
}
So when I launch my app, the button is hidden and stays hidden when I select a row. I debugged, and found that btn is nil when I called the method in ClassA. After some research, I found that the method is called for another instance, so here my question, what can I do, to get it called for the right instance?
EDIT
Here part of my ClassA.m
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSInteger row = [indexPath row];
[[ClassB sharedInstance] showBtn:YES];
}
Observation: The ClassB is a UIViewController which is wrong. UIViewControllers have viewDidLoad.
Implementation Suggestion:
The correct implementation for the requirement would be that you create a custom cell with a button. Hide the button in awakeFromNib method. in didSelectRowAtIndex set the cell.button.isHidden = YES.
This should alone take care of the requirement mentioned above.
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.
Here is my .h file
#import <UIKit/UIKit.h>
#interface PersonViewController : UIViewController
#property(strong,nonatomic) NSString *personTitle;
And here is my .m file
#interface PersonViewController ()
#property (weak, nonatomic) IBOutlet UILabel *titleView;
#end
#implementation PersonViewController
//stuff …
-(void)setPersonTitle:(NSString *)personTitle
{
[self.titleView setText:personTitle];// also self.titleView.text=personTitle
[self.titleView setNeedsDisplay];
NSLog(#"The title shoud match as %# :: %#",personTitle,self.titleView.text);
}
-(NSString *)personTitle
{
return self.titleView.text;
}
//… more stuff
#end
The logging shows that the value is (null) for self.titleView.text whereas personTitle prints the appropriate value.
I remember doing this same thing a number of times and it worked. Any ideas why it’s failing this time?
update I use storyboard to set my scenes. And I am using xcode-5 and iOS-7
update: how I call
The user clicks a button, leading to a push segue
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSLog(#"enter prepare for segue.");
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
if ([segue.identifier isEqualToString:the_identifier_for_person]) {
NSLog(#"segue to person is progressing“);
if ([segue.destinationViewController isKindOfClass:[PersonViewController class]]) {
NSLog(#"segue to person destination is a match");
PersonViewController *aPerson = (PersonViewController *)segue.destinationViewController;
aPerson.personTitle=((MyItem*)self.allItems[indexPath.row]).title;
NSLog(#"segue to person is done");
}
}
}
This sounds like you forgot to wire up your UILabel in the storyboard. Can you confirm that self.titleView is not null?
View controllers create their views on demand, but can spot that only via a call to view. When the view is loaded, your outlets will be populated.
Either call view to force loading or keep the string in abeyance until you get viewDidLoad.
(aside: prior to iOS 6, views would also be released in low-memory situations so the idiomatic thing is to store the string and populate on viewDidLoad)
Having accepted another answer, I wanted to show the pattern that I actually used to solve the problem, in case someone else comes looking. This pattern is best practice (yes, I forgot it for a long moment there).
#pragma mark - update UI
-(void)setPersonTitle:(NSString *)personTitle
{
_personTitle=personTitle;
if (self.view.window) [self updateUI];//only if I am on screen; or defer to viewWillAppear
}
-(void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self updateUI];
}
-(void)updateUI
{
self.titleView.text=self.personTitle;
}
It is always important to update the ui when the data has changed, which is why I must make the call inside setPersonTitle. But because the IBOutlets are not yet set when I set personTitle in prepareForSegue, then I must also make the call inside viewWillAppear.
Do you actually call the -(void)setPersonTitle:(NSString *)personTitle method?
It seems that you aren't calling it correctly which would result in the title being null.
After reviewing the prepareForSeque it is clear that you are not calling the method. You are actually just changing the #property named personTitle.
In the viewDidLoad you should have it so that self.titleView.text = self.personTitle;
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!
I am currently designing the structure for my first iPhone game and ran into a problem. Currently, I have a 'MenuViewController' that allows you to pick the level to play and a 'LevelViewController' where the level is played.
A UIButton on the 'MenuViewController' triggers a modal segue to the 'LevelViewController'.
A UIButton on the 'LevelViewController' triggers the following method to return to the 'MenuViewController':
-(IBAction)back:(id)sender //complete
{
[self dismissModalViewControllerAnimated:YES];
}
The problem is, I have a UILabel on the menu page that prints the number of total points a player has. Whenever I go back to the menu from the level, I want this label to automatically update. Currently, the label is defined programmatically in the 'MenuViewController':
-(void)viewDidLoad {
[super viewDidLoad];
CGRect pointsFrame = CGRectMake(100,45,120,20);
UILabel *pointsLabel = [[UILabel alloc] initWithFrame:pointsFrame];
[pointsLabel setText:[NSString stringWithFormat:#"Points: %i", self.playerPoints]];
[self.pointsLabel setTag:-100]; //pointsLabel tag is -100 for id purposes
}
self.playerPoints is an integer property of MenuViewController
Is there a way I could update the label? Thanks ahead of time!
This is a perfect case for delegation. When the LevelViewController is done, it needs to fire off a delegate method which is handled in the MenuViewController. This delegate method should dismiss the modal VC and then do whatever else you need it to do. The presenting VC should normally handled the dismissal of modal views it presents.
Here is a basic example of how to implement this:
LevelViewController.h (Above the Interface declaration):
#protocol LevelViewControllerDelegate
-(void)finishedDoingMyThing:(NSString *)labelString;
#end
Same file inside ivar section:
__unsafe_unretained id <LevelViewControllerDelegate> _delegate;
Same File below ivar section:
#property (nonatomic, assign) id <LevelViewControllerDelegate> delegate;
In LevelViewController.m file:
#synthesize delegate = _delegate;
Now in the MenuViewController.h, #import "LevelViewController.h" and declare yourself as a delegate for the LevelViewControllerDelegate:
#interface MenuViewController : UIViewController <LevelViewControllerDelegate>
Now inside MenuViewController.m implement the delegate method:
-(void)finishedDoingMyThing:(NSString *)labelString {
[self dismissModalViewControllerAnimated:YES];
self.pointsLabel.text = labelString;
}
And then make sure to set yourself as the delegate for the LevelViewController before presenting the modal VC:
lvc.delegate = self; // Or whatever you have called your instance of LevelViewController
Lastly, when you are done with what you need to do inside the LevelViewController just call this:
[_delegate finishedDoingMyThing:#"MyStringToPassBack"];
If this doesn't make sense, holler and I can try to help you understand.
Make a property self.pointsLabel that points to the UILabel, then you can just call something like [self.pointsLabel setText:[NSString stringWithFormat:#"Points: %i", self.playerPoints]]; to update the label with the new score
In your modal view header file, add the property:
#property (nonatomic,assign) BOOL updated;
Then in your main view controller, use didViewAppear with something like:
-(void)viewDidAppear:(BOOL)animated{
if (modalView.updated == YES) {
// Do stuff
modalView.updated = NO;
}
}
Where "modalView" is the name of that UIViewController that you probably alloc/init there.
Add more properties if you want to pass more info, like what level the user picked.