Would like to execute some code in a loop, but I want to pause it while a uiscrollview is scrolling.
I've created a BOOL named isScrolling that is set to "YES" in the scrollViewDidScroll method and is set to "NO" in the scrollViewDidEndDecelerating method.
I'd like to put the loop in the scrollViewDidEndDecelerating method, but have it paused when isScrolling == YES and then restart it when isScrolling == NO.
I am looping through an array, so I'd like the loop to pick up where it left off in the array.
For example, here's a simple loop that continually counts higher by increments of 1 that I placed in the viewDidAppear method:
for (int i = 1; i > 0; i++) {
NSLog(#"i = %d", i);
if (isScrolling == YES) {
NSLog(#"break");
break;
}
}
But, all scrolling is disabled while this loop is running for some reason so I can't cancel it.
How can I do this?
Thanks!
You say this loop is within viewDidLoad, which means it's being executed by the main application thread (and thus the "UI thread). If that's the case, there's nothing you can do. Your UI isn't going to be doing anything at all while that loop is running because it's the same thread executing the loop.
Without knowing more about what it is you're actually doing in that loop, it's hard to suggest what you could do to offload that work into a separate thread.
I'm not entirely sure what you're aiming for in a use case for this - I'm guessing you have something happening in a loop that's slowing down your UIScrollView scrolling and making it stutter a bit more than you'd like.
I'm going to guess that the answer is make the background task that you're working with asynchronous so that it's not taking up so much CPU resource in general. If you really want to bind into reacting if the scroll view is moving, then you want the UIScrollView Delegate methods to be your primary trigger points. The ones that stand out as potential uses:
– scrollViewWillBeginDragging:
– scrollViewDidEndDragging:willDecelerate:
– scrollViewDidEndScrollingAnimation:
...
Don't invoke the looping in viewDidLoad or you'll block the main UI interaction thread. Instead trigger off a background process using classic threading or better yet NSOperationQueue to do whatever looping/background thing you want done. If you have that queue reference in the view controller, you could also hook into that to pause or cancel those operations when a person decides to scroll using the delegate methods above.
Related
Can I have your advice on the approved approach?
I have four processes that need to run sequentially:
calculateProcess1()
calculateProcess2()
calculateProcess3()
calculateProcess4()
These calculate data and update progress bars (circular) and other screen literals using dispatch queues to make the UI update.
Run them as is and lo and behold they all fire off simultaneously (what a surprise!).
How do I make the top level calls run in sequence (as above) and do I need to make changes to my DispatchQueues in the processes for updating the literals, e.g.:
DispatchQueue.main.sync {
let progressPercentage = (day*100/365)
self.progressLabel.text = String(Int(progressPercentage))+"%"
}
The initiating processes (calculateProcess1-4()) need to run from main. Should this be in ViewDidLoad, a button action or what is the most secure method?
A one approach would be to use Operation and OperationQueue. Setting maxConcurrentOperationCount: Int to 1 on the OperationQueue, will force operations to be performed in a sequence, one after another.
(Previously called NSOperation and NSOperationQueue)
This blogpost can be helpful: https://nshipster.com/nsoperation/
And of course awesome WWDC15 session: Advanced NSOperations
1- Don't sync in main thread as it'll cause a runtime crash , it should be async
DispatchQueue.main.sync {
2-
Should this be in ViewDidLoad, a button action or what is the most secure method?
it's up to you there is no thing related to security here , implement it as your UX
3- to run them in dequence create a custom Queue then dispatch them in it as it' ll run serially if the code inside all these methods run in the same thread of the queue meaning you don't internally dispatch inside another queue , you can also use DispatchGroup to be notified when all are done
If you want to keep it simple you can provide callback blocks to your calculateProcess methods and simply nest them.
calculateProcess1() {
calculateProcess2() {
calculateProcess3() {
calculateProcess4() {}
}
}
}
Should this be in ViewDidLoad, a button action or what is the most
secure method?
viewDidLoad is probably what you want. Keep in mind if you instantiate a new viewController of the same type and show it, it will happen again, once, for that new controller as well. Also, if you are doing anything with view frames, those are not guaranteed to be laid out in viewDidLoad, but if you are simply updating text then it should be fine.
I need ideas on the following -
In the main thread at some point of execution say Point A(sequential logic), I need to remember the state of execution and delegate the execution of some other logic onto another thread, and let the main thread handle the UI events etc. When the delegated logic completes on the other thread then the flow of execution should continue from the point A and should recollect the entire execution context and proceed as if it never paused there.
Regards,
Sunil Phani Manne
It's hard to implement this exactly the way you're saying (for example do(things)... yield(other_thread); ...do(more_things);.
Here are a couple other options I can think of (you'd have to implement these yourself, using delegates or notifications for example; I'm just giving a basic outline of how it would work):
do(things)
[object doStuffOnOtherThreadWithCallback:^{ // block-based
do(more_things)...
}];
or...
do(things)
[object doStuffOnOtherThreadWithCallbackTarget:self // target/selector-based
selector:#selector(callbackSelector)];
}
- (void)callbackSelector {
do(more_things)...
}
One option you have is encapsulating the whole sequential logic that comes after Point A in your delegate and then execute it on the main thread when the secondary thread ends.
In other words, when you start the thread by calling, e.g.
[NSThread detachNewThreadSelector:sel toTarget:target withObject:delegate]
you can implement your target target so that it has a specific selector completion that is called at the end of sel on the main thread, like this (this is the your delegate class):
#implementation YOURDelegateClass {
.....
-(void)completion {
}
-(void)sel {
...
...
[self performSelectorOnMainThread:#selector(#"completion") withObject:self];
}
}
Of course you have many sub-options available here, like using a different call to start the background execution, etc.
The important point is that: you have to encapsulate in a selector all the logic that comes after Point A, and that you have to schedule the execution of this selector on the main thread, in order to get back to your context (although your context will have changed in the meantime because you will also have updated the UI).
EDIT:
Having to schedule the execution on the main thread defeats blocks from being suitable for this kind of callback. On the other side, block have the advantage that they in some limited sense give you access to the same lexical context in which the block was defined (which is roughly what you call context).
A workaround for this could be the following. Before detaching the new thread, store in a delegate the block you would like to execute at completion:
typedef void(^CustomBlock)(void);
#property (nonatomic, copy) CustomBlock customBlock;
....
int a = ...
delegate.customBlock = ^{
NSLog(#"hello %d.....", a);
}
[NSThread detachNewThreadSelector:sel...
....
-(void)completion {
[self customBlock];
}
Of course, you only get the context preservation that is guaranteed to you by block. But here you hit against a limit of the language.
If you need more context preservation, then the only possibility is encapsulating that context in your delegate class ivars.
One thing is for sure. There, most probably, isn't any direct feature in Cocoa that does that. Since you're saying that you can't duplicate the resources onto the new thread (for a very good reason), I am going to suggest that you make use of NSUndoManager. For every change you make in the thread, push an undo operation for that change onto the undo manager. At the end of the thread, execute all the undo operations in the undo manager object. This should, if done correctly, restore your state. Now, since the idea is untested, there could be a chance that not all actions can be undone. You will have to check that out first.
I use the block base API for my animations on iOS.
One animation has a completion block and that block is called at the end of the animation, nice.
However, that animation can be fired multiple times when the user scrolls (the animation is on a UITableViewCell). When that happens the the completion block is called multiple times. The finished parameter of the block is always YES.
Since the animation is not actually finished (an other animation took place) I thought that the finished parameter would be NO, but it's not.
Did I miss something? How can I avoid the completion block to be called multiple times?
The completion block is called multiple times simply because, in your case, your animation is fired multiple times. What is happening is that iOS invokes your animation block each time it is told so, probably in a separate thread. Then, for each animation it tracks its completion, and upon completion it calls the associated completion block. So basically, you see your completion block firing multiple times, one for each invocation of your animation. Note that the boolean value associated to a completion block is specific of that completion block, it does not refer in any way to a different animation.
To recap, what you are experiencing is simply the effect of concurrency. If this is not your intended behavior, then you need to modify your code accordingly. If you want your animations to fire one at a time, you may use NSLock (NSConditionLock for advanced control using an associate condition variable) or, if you prefer, a mutex and the Posix pthreads library directly, to create a critical section to be executed in a mutually exclusive fashion.
Not sure when you're firing the animations and whether they loop (like a UIActivityView spinner or something) - sounds like it's every single pixel the table view is scrolling?
In any event, perhaps you could use the UIScrollView delegate methods and tell each cell to start animation on scrollViewWillBeginDragging: and tell each cell to stop at scrollViewDidEndDragging:
You could set a boolean isAnimating for your UITableViewCell and if an animation is currently underway, do nothing.
if (isAnimating) {
// ... do nothing
} else {
// Start your animation
}
Or stick with whatever you have now and use a boolean still, but only fire the animation if it's not currently animating. Then in your finished parameter just set isAnimating to NO.
if (isAnimating) {
// ... do nothing
} else {
[UIView animateWithDuration:0.3f
animations:^{
// animations...
isAnimating = YES;
}
completion:^{
isAnimating = NO;
}
];
}
I've resolved this issue by looking if the completion block is relevant at the beginning of that block.
The finished parameter is not relevant right now. I've communicated with Apple and they told me that it's fixed in iOS 4.2.
I have a UITableView which gets data from a server and updates in every 1 second(using performSelectorOnMainThread). Since this blocks main thread sometimes its not easy to scroll the table and its painfull for the user. Also i cant reduce my refresh interval also.
What are the possible solutions for this problem?
I would only refresh the visible cells as the data changes, and the others as they appear so it will be less consuming than updating the hole UITablaView
you can get the visible cells using ( from UITableView):
- (NSArray *)visibleCells
and you can update the remaining cells as they appear using UITableViewDelegate Protocol
– tableView:willDisplayCell:forRowAtIndexPath:
and i think this should make it a bit faster.
Hold your data in a mutable array or similar structure then asyncronously update that array with an NSURLConnection. You can call reloadData on the tableview to redraw the table when the NSURLConnection is done.
You would probably just call the NSUrlConnection from an NSTimer at whatever interval you prefer.
instead of calling performSelectorOnMainThread function of NSThread call
detachNewThreadSelector function. In this way your thread will not block the main thread
[NSThread detachNewThreadSelector:#selector(aMethod:) toTarget:[MyObject class] withObject:nil];
in toTarget: method you can write self instead of [MyObject class]
also in Implementation selector write #synchronize(self) for eg.
-(void)aMethod
{
#synchronize(self) {
//write your whole code here
}
}
I have done the same thing in my application its work perfectly
Use GCD with queues (serial or global queues). This is the apple recommended way now.
dispatch_async(dispatch_get_main_queue(), ^{
//do UI updates
});
I Have the following code:
-(void) changeAnimation:(NSString*)name forTime:(int) times {
if(currentAnimation != #"attack")
{
id action = [CCAnimate actionWithAnimation:[self animationByName:name]];
id repeatAction = [CCRepeat actionWithAction:action times:times];
currentAction = [self runAction:repeatAction];
lastANimation = currentAnimation;
currentAnimation = name;
}
else if(currentAction.isDone)
{
//Here is where I would change the animation
//but I commented the code for now
}
}
So when I run this and click on the button that changes the animation to "attack" (by calling [mysprite changeAnimation:#"attack" forTime:1];), I get a EXC_BAD_ACCESS error from the "currentAction.isDone" line, the next time the function is called (the joystick will call changeAnimation to try and change the animation to "run" or "idle", but I want the attack animation to finish first). Any thoughts on whyI get this? currentAction is declared in my class.
Edit: there is nothing in the rest of the class that interacts with currentAction, beside a getter. Its declaration is in the .h (CCAction* surrentAction). Do I need to initialize it? I thought the returned value from runAction would be sufficient? ANyways, when I run the debugger, it is not nil, and assigned to the correct action.
Thanks,
Dave
Edit:
I ended up creating a sequence when "attacking" that calls a function that changes the currentAnimation, so i avoided the issue. Still no idea what was happening.
Here's the answer if your interested:
Other Post
More of the class is probably needed to really answer this properly, but the EXC_BAD_ACCESS typically happens because you're accessing something that has been released and is no longer available in memory.
I'm guessing that somewhere in your class you're releasing, either explicitly, or implicitly, the "currentAction" object asynchronously - and when you're checking later, it's done & gone and you're hitting this crasher.
In general, keeping a state variable or two that you always have known values on is a good way to go, and for the "actions" that you're going through, if they're asynchronous and doing their own memory management, leave them as such and work through some state variables that you maintain and control all the memory management around. It's a pretty reasonable pattern for asynchronous callbacks, either with the classic stuff or as you move into using blocks with iOS 4.0