One of my methods (mySecondMethod) receives a block and need to add an additional treatment to this block before passing it as an argument to another method.
Here is the code sample:
- (void)myFirstMethod {
// some code
__weak MyController *weakSelf = self;
[self mySecondMethod:^(BOOL finished) {
[weakSelf doSomething:weakSelf.model.example];
}];
}
- (void)mySecondMethod:(void(^)(BOOL finished))completion {
void (^modifiedCompletion)(BOOL) = ^void(BOOL finished){
completion(finished);
_messageView.hidden = YES; //my new line
};
[UIView animateWithDuration:duration animations:^{
//my code
} completion:modifiedCompletion];
}
When run, I got a bad access error on completion(finished) line. completion is NULL. I tried to copy the block like this:
void (^copiedCompletion)(BOOL) = [completion copy];
void (^modifiedCompletion)(BOOL) = ^void(BOOL finished){
copiedCompletion(finished);
_messageView.hidden = YES; // my new line
};
but still got the error.
When I empty out completion block, the crash still happen so the crash is not due to what is inside.
Any idea how to solve this? Thanks!
I think you are getting the bad access because of this
// some code
__weak MyController *weakSelf = self;
[self mySecondMethod:^(BOOL finished) {
[weakSelf doSomtehing:weakSelf.model.example];
}];
Try changing it to.
id example = self.model.example;
[self mySecondMethod:^(BOOL finished) {
[self doSomething:example];
}];
Edit
The block needs to be copied before being called.
Side Note
Do a check before calling blocks to avoid unexpected crashes.
if (completion) {
completion(finished);
}
Related
I want to show loading animation while I am doing some initialisation. So I want to spin my (UIImageView*)_LogoImage until my initialisation will be completed. Then, after initialisation will be finished, I want to scale my _LogoImage. So, all this things begins from viewDidAppear, where I am calling beginLoading: method.
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
[self beginLoading];
}
Here, I am starting my spin animation. Assume, I am doing some initialisation in the next two code lines, I changed them to make a thread sleep to make a similar behaviour of my initialisation code. Then I am calling stopSpin: method to make the next half circle and to do my last scaling animation.
-(void)beginLoading{
[self startSpin];
[self sleepAction:3.0f];
[self sleepAction:1.0f];
[self stopSpin];
}
-(void)sleepAction:(float)sleepTime{
NSLog(#"SleepTime:[%f]",sleepTime);
[NSThread sleepForTimeInterval:sleepTime];
}
Here's my spinning code, which I am calling recursively until my BOOL refreshAnimating is Equal to YES. if not - running the last scaling animation
-(void)startSpin{
[UIView animateWithDuration:0.4
delay:0
options:UIViewAnimationOptionCurveLinear
animations:^{
_LogoImage.transform = CGAffineTransformMakeRotation(M_PI);
}
completion:^(BOOL finished){
[UIView animateWithDuration:0.4
delay:0
options:UIViewAnimationOptionCurveLinear
animations:^{
_LogoImage.transform = CGAffineTransformMakeRotation(0);
}
completion:^(BOOL finished){
if (refreshAnimating) {
[self startSpin];
}else{
[self refreshFinished];
}
}];
}];
}
-(void)stopSpin{
refreshAnimating = NO;
}
-(void)refreshFinished{
[UIView animateWithDuration:0.4 animations:^{
[_LogoImage setTransform:CGAffineTransformMakeScale(2, 2)];
}];
}
The problem is that my animation is not running, until the last initialisation code line completes. And after completion - I can see only the last animation. I was trying to put some code performing in the background and main thread, but I didn't run the way I want.
The rotation animation should start when the view appears, and continue until my initialisation code complete - then I want to see my last scale animation, which will indicate that my initialisation is completed.
Please, help.
All animations run on main thread and if you use sleep, it blocks your animations(main thread). Instead you can use a library like: MBProgressHUD please feel free to ask further questions.
Edit
-(void)startSpin{
_LogoImage.transform = CGAffineTransformMakeRotation(M_PI);
[UIView animateWithDuration:0.4
delay:0
options:UIViewAnimationOptionCurveLinear
animations:^{
_LogoImage.transform =CGAffineTransformMakeRotation(0);
}
completion:nil];
}
Can you try this. I think that is what are trying to do.
As #Ersin Sezgin said, all animations run on main thread.
So now, I am doing initialisation in background with completion block handler, which is forcing stopSpin: after initialisation completes.
Here's some code block. For better readability there's typedef of block.
typedef void(^OperationSuccessCompletionHandler)(BOOL success);
in .m file
-(void)sleepAction:(OperationSuccessCompletionHandler)success{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// We are doing initialisation here
BOOL isOperationSuccess = YES // Assume initialisation was successful
success(isOperationSuccess);
});
}
So now we can do this in another way
-(void)beginLoading{
[self startSpin];
[self sleepAction:^(BOOL success){
[self sleepAction:^(BOOL success){
[self stopSpin];
}];
}];
}
I have a block function defined as the below:
#property (atomic, assign) bool callInProgress;
//in implementation:
- (void)synchronize:(void(^)(void(^unlock)()))block {
if (!_callInProgress) {
_callInProgress = YES;
[_tableView setScrollEnabled:false];
block(^{
[_tableView setScrollEnabled:true];
_callInProgress = NO;
});
}
}
Then when I do:
[self synchronize:^(void(^unlock)()) {
}];
and I set a break point at that [self synchronize..], the break point gets hit twice no matter what! If I add a body:
/*break point on this line*/ [self synchronize:^(void(^unlock)()) {
NSLog(#"HERE");
unlock();
}];
HERE gets printed ONCE but the break point gets hit twice!
Any ideas why?
The breakpoint is being hit once when you reach the synchronize call, and once when you enter the callback block. Both are on the same line.
When I first call following code from dispatch queue, completion block not called.
- (void)viewDidLoad
{
[super viewDidLoad];
dispatch_queue_t initialize_queue;
initialize_queue = dispatch_queue_create("init", NULL);
dispatch_async(initialize_queue, ^{
_onInit = YES;
_isRunning = NO;
[self startProgress];
_onInit = NO;
});
}
- (void)startProgress
{
if (!_isRunning) {
_isRunning = YES;
NSLog(#"Starting");
[UIView animateWithDuration:0.5 animations:^{
self.progressStatusButton.hidden = NO;
self.activityLeftConstraint.constant = _activityLeftSpace;
self.activityWidthConstraint.constant = _activityWidth;
self.buttonWidthConstraint.constant = _progressStatusButtonWidth;
self.buttonLeftConstraint.constant = _progressStatusButtonLeftSpace;
self.activityView.alpha = 1.0;
}completion:^(BOOL finished){
NSLog(#"Start Animating");
[self.activityView startAnimating];
}];
}
}
When I delete dispatch_aync method and execute startProgress method in viewDidLoad,
completion block called. How should I correct my code?
I tried to change dispatch_async to use dispatch_async(dispatch_get_main_queue(),..),
then completion block called, but I would like to execute asynchronously startProgress method.
Please Let me know.
Everything connected with GUI should be called from main thread/ pushed to main queue. In other case expected behavior is not guaranteed.
So you should use
dispatch_get_main_queue()
Also animation ALWAYS executed asynchronously so I do not think you really need separate queue for it.
Not sure how to describe this, but what would be the easiest way to have a UILabel display Loading, then Loading., then Loading.., then Loading..., and then repeat?
I have been looking into timers but it all seems a bit excessive. Anyone know of a cool and quick trick to pull something like that off? Thanks!
Not sure about your specific use case (more info please or code?) but have you tried animation blocks? Something like:
- (void)animate
{
__block UIView * blockSelf = self;
[UIView animateWithDuration:0.1f animations:^{
if ([blockSelf.label.text isEqualToString:#"Loading..."]) {
blockSelf.label.text = #"Loading";
} else {
blockSelf.label.text = [NSString stringWithFormat:#"%#.", blockSelf.label.text];
}
} completion:^(BOOL finished) {
if (blockSelf.processIsFinished) {
[self moveOn];
} else {
[blockSelf animate];
}
}];
}
Alternatively, something like MBProgressHUD might be useful depending on what type of process is "loading".
I'm new to programming for the iPhone and i'm having a problem with this function
-(IBAction)changeX {
[self timere:field1];
[self timere:field2];
}
this is a button to move an uitextfield object across the screen. The problem is i want to run this method on the first field , complete it then go on to the second. The timere function uses NSTimer to continously move the object until it reaches a certain point at which it terminates. I have two other functions shown below. The actual program im making is much longer but the objective is the same and that code is too long. The problem is running the first function then the second. Thank you for the help.
-(void)timere:(UITextField*)f1 {
UITextField*fielder1=f1;
NSInvocation*timerInvocation = [NSInvocation invocationWithMethodSignature:
[self methodSignatureForSelector:#selector(moveP:)]];
[timerInvocation setSelector:#selector(moveP:)];
[timerInvocation setTarget:self];
[timerInvocation setArgument:&fielder1 atIndex:2];
timer1 = [NSTimer scheduledTimerWithTimeInterval:0.03 invocation:timerInvocation repeats:YES];
}
-(void) moveP:(UITextField*)f1 {
UITextField*fielder1=f1;
fielder1.center = CGPointMake(fielder1.center.x+4, fielder1.center.y);
if (fielder1.center.x>237) {
[timer1 invalidate];
}
}
The standardized method of animating UIView objects is by using methods such as the following.
+ animateWithDuration:delay:options:animations:completion:
The syntax for these methods might be a bit daunting if you are unfamiliar with Objective-C Blocks. Here's a usage example which moves the first field, then the second field afterwards.
[UIView animateWithDuration:0.3 animations:^{
[field1 setCenter:CGPointMake(FINAL_CENTER_X1, FINAL_CENTER_Y1)];
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.3 animations:^{
[field2 setCenter:CGPointMake(FINAL_CENTER_X2, FINAL_CENTER_Y2)];
}];
}];
EDIT: For a general fix on your specific problem, I would make the following modifications:
-(IBAction)changeX {
[self timere:field1];
}
-(void) moveP:(UITextField*)f1 {
UITextField*fielder1=f1;
fielder1.center = CGPointMake(fielder1.center.x+4, fielder1.center.y);
if (fielder1.center.x>237) {
[timer1 invalidate];
// I'm really not sure about the scope of field1 and field2, but
// you can figure out the encapsulation issues
if (f1 == field1) {
[self timere:field2];
}
}
}
A more generic and indeed low-level solution would be to have a state variable. Perhaps you would have some sort of NSArray *fields of UITextField * and int state = 0. Where I added the conditional in moveP above, you would state++ and call [self timere:[fields objectAtIndex:state]] if state < [fields count]. You timer invalidation code is correct, anyway.