I have two scroll views in my app, one containing UIImageViews, one containing UIButtons. Using NSTimer, I'm making them scroll automatically. However, if one of the scroll views is tampered with (a finger touches it and starts scrolling manually), the other scroll view stops as well. Is there any way to stop this from happening? Or is it normal?
Also, the UIButtons inside the second scroll view are tap-able, but they don't show the standard highlighting. If I enable the glow effect, it works, but not the standard highlighting. Is there anyway I can make this work as well?
My code for NSTimer is
[NSTimer scheduledTimerWithTimeInterval:0.018
target:self
selector:#selector(onTimerScrollText)
userInfo:nil
repeats:YES];
- (void)onTimerScrollText {
CGFloat x = self.textScroller.contentOffset.x;
x += 0.5;
[self.textScroller setContentOffset:CGPointMake(x, 0)];
}
And it's pretty much the same for the image scroller.
Thanks!
The initial problem of one scrollview no longer scrolling until the other manual scroll has finished has been solved.
The solution is simple. Each timer needs to be added to the run loop:
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
Now they both work regardless of whether one is interrupted by one's finger.
Related
There are a few similar questions out there on SO (links at end), but none of them has allowed me to fix my problem, so here goes:
I'm using OpenGL rendering to make an image tiling and caching library for use in a game project, and I want to hijack the physics of the UIScrollView to allow the user to navigate around the images (since it has nice bounce behaviour, might as well use it). So I have a UIScrollView which I'm using to get the rendering view for my textures, but there's a problem - moving around on the scroll view prevents the CADisplayLink from firing until the user has finished scrolling (which looks horrible). One temporary fix has been to use NSRunLoopCommonModes instead of the default run mode, but unfortunately this breaks some aspects of scroll view behaviour on certain phones I'm testing on (the 3GS and simulator seem to work fine, while the iPhone4 and the 3G don't).
Does anyone know how I could get around this clash between the CADisplayLink and the UIScrollView, or know how to fix the UIScrollView working in other run modes? Thanks in advance :)
Promised links to similar questions:
UIScrollView broken and halts scrolling with OpenGL rendering (related CADisplayLink, NSRunLoop)
Animation in OpenGL ES view freezes when UIScrollView is dragged on iPhone
It's possible that slow updates on the main thread triggered by the CADisplayLink are what's breaking UIScrollView's scrolling behavior here. Your OpenGL ES rendering might be taking long enough for each frame to throw off the timing of a UIScrollView when using NSRunLoopCommonModes for the CADisplayLink.
One way around this is to perform your OpenGL ES rendering actions on a background thread by using a Grand Central Dispatch serial queue. I did this in my recent update to Molecules (source code for which can be found at that link), and in testing with using NSRunLoopCommonModes on my CADisplayLink, I don't see any interruption of the native scrolling behavior of a table view that's onscreen at the same time as the rendering.
For this, you can create a GCD serial dispatch queue and use it for all of your rendering updates to a particular OpenGL ES context to avoid two actions writing to the context at the same time. Then, within your CADisplayLink callback you can use code like the following:
if (dispatch_semaphore_wait(frameRenderingSemaphore, DISPATCH_TIME_NOW) != 0)
{
return;
}
dispatch_async(openGLESContextQueue, ^{
[EAGLContext setCurrentContext:context];
// Render here
dispatch_semaphore_signal(frameRenderingSemaphore);
});
where frameRenderingSemaphore is created earlier as follows:
frameRenderingSemaphore = dispatch_semaphore_create(1);
This code will only add a new frame rendering action onto the queue if one isn't in the middle of executing. That way, the CADisplayLink can fire continuously, but it won't overload the queue with pending rendering actions if a frame takes longer than 1/60th of a second to process.
Again, I tried this on my iPad here and found no disruption to the scrolling action of a table view, just a little slowdown as the OpenGL ES rendering consumed GPU cycles.
My simple solution is to halve the rendering rate when the run loop is in tracking mode. All my UIScrollViews now work smoothly.
Here is the code fragment:
- (void) drawView: (CADisplayLink*) displayLink
{
if (displayLink != nil)
{
self.tickCounter++;
if(( [[ NSRunLoop currentRunLoop ] currentMode ] == UITrackingRunLoopMode ) && ( self.tickCounter & 1 ))
{
return;
}
/*** Rendering code goes here ***/
}
}
The answer at the following post works very well for me (it appears to be quite similar to Till's answer):
UIScrollView pauses NSTimer until scrolling finishes
To summarize: disable the CADisplayLink or GLKViewController render loop when the UIScrollView appears and start a NSTimer to perform the update/render loop at the desired framerate. When the UIScrollView is dismissed/removed from the view hierarchy, re-enable the displayLink/GLKViewController loop.
In the GLKViewController subclass I use the following code
on appear of UIScrollView:
// disable GLKViewController update/render loop, it will be interrupted
// by the UIScrollView of the MPMediaPicker
self.paused = YES;
updateAndRenderTimer = [NSTimer timerWithTimeInterval:1.0f/60.0f target:self selector:#selector(updateAndRender) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:updateAndRenderTimer forMode:NSRunLoopCommonModes];
on dismiss of UIScrollView:
// enable the GLKViewController update/render loop and cancel our own.
// UIScrollView wont interrupt us anymore
self.paused = NO;
[updateAndRenderTimer invalidate];
updateAndRenderTimer = nil;
Simple and effective. I'm not sure if this could cause artifacts/tearing of some sort since the rendering is decoupled from screen refreshes, but using CADisplayLink with NSRunLoopCommonModes totally breaks the UIScrollView in our case. Using NSTimer looks just fine for our app and definitely a whole lot better than no rendering.
Even though this is not the perfect solution, it still might work as a workaround;
You could ignore the display link availability and use NSTimer for updating your GL-layer instead.
I have a UIImageView whose image I want to change depending on user input. For example, the user clicks a button, after which the background image is swapped out for another image. The program blocks for a second or two, and then the new image is swapped out for the original image. The problem is, it seems that the view doesn't redraw itself until the method has returned, so that the middle UIImage is never displayed.
How can I override this behavior to get the UIImageView (or the view itself) to redraw itself in the middle of a method?
When user click, start a NSTimer :
- (IBAction)btnTouched:(id)sender
{
[NSTimer scheduledTimerWithTimeInterval:_YOUR_TIME_
target:self
selector:#selector(timerFinished:)
userInfo:nil
repeats:NO];
}
- (void)timerFinished:(NSTimer *)timer
{
// Change your image
}
It should not block your program.
I have an animated transparent OpenGL ES subview (a modification of Apple's template EAGLView class) which draws a rotating sphere. Just like Apple's example, CADisplayLink is used on devices where available.
On the same screen, there is a UIScrollView containing UIButtons that can be selected. When the user scrolls the UIScrollView, the animation of my EAGLView freezes. This behavior is reproduced on iOS Simulator 4.2 and on iPhone OS 3.1.3 on an iPhone 2G device.
Any ideas on what to do to prevent pause of the EAGLView, apart from coding my own scroll view?
Whether CADisplayLink fires during scrolls depends on the mode with which you add it to the run loop. Probably you have this, somewhere:
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
UIApplication adds a run loop mode, UITrackingRunLoopMode, for 'tracking in controls', which includes when a scrollview is scrolling. So at that point the runloop is switched out of the default mode and hence your display link (and also any timers, NSURLConnections, etc, added in the default mode) will fail to fire until default mode is restored.
Quick fix: change your code to:
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
UITrackingRunLoopMode is considered one of the common modes.
Spending too much time interrupting UIKit can lead to some very poor control responsiveness, so you need to be careful. It'd be to diverge from the topic massively, but although OpenGL is modal and therefore not particularly threading friendly, you can use an EAGLSharegroup to do rendering on a separate thread and then push it onto the main thread.
An example in (2016) Swift3...
let d = CADisplayLink(target: self, selector: #selector(ThisClassName.updateAlpha))
d.add(to: RunLoop.current, forMode: RunLoopMode.commonModes)
//and then, for example...
func updateAlpha() {
let a = leader.layer.presentation()?.value(forKey: "opacity") as! CGFloat
follower.alpha = a
}
How would i show that the progress bar is working there is no Refresh() in objective-c like there is in .net what would i use
for example
contactprogress.progress = 0.5;
StatusLabel.text = #"Status: Address Found";
How would i refresh the view to show that the progress has changed & show user the StatusLabel status?
Thanks
Mason
Based on your comments to the other two responses, you're doing some computationally intensive task that takes a few seconds, and the view is not updating during the task. You could try using two threads (not as scary as it sounds) so that the UI can continue updating while the task is doing its thing.
Here is an example.
iOS Reference Library Threading Programming Guide
It sounds like you want to update a progress bar and some text repeatedly with some time interval between. You could use a simple timer. Read about timers here. Perhaps something like this:
[NSTimer scheduledTimerWithTimeInterval:0.1f target:self selector:#selector(refreshProgress) userInfo:nil repeats:YES];
and:
- (void) refreshProgress:(id)sender {
// figure out new progress bar and text values
contactprogress.progress = 0.5;
StatusLabel.text = #"Status: Address Found";
// check if we should stop the timer and hide the progress bar/status text
}
That would update your progress bar with new values every 0.1 seconds.
To redraw an UIView, you can use
[UIView setNeedsDisplay];
However, you will need this only in very rare cases. When you change, let's say the contents of a label, your UI should update instantly. You might want to provide more code/context for your problem.
I have a looping scrollView but I want the user to be able to speed up the scrolling by interacting with the scrollView (ie scrolling with their finger) if they choose. Do I have to do extra work to acheive this as at the moment the animated scrolling takes priority over the users interaction and slows the scroll down dramatically. Has anyone done something similar? As an example Im looking to acheive as similar affect to that used in the about screen on the angry birds game?
Many thanks
Here us the code for basice animation of the scroll view
[UIView beginAnimations:#"scroll " context:nil];
[UIView setAnimationDuration:15];
[scrollView setContentOffset:CGPointMake(0, 600) animated:NO];
[UIView commitAnimations];
As soon as the user tries to scroll, the scrollView slightly scrolls (it moves very slightly), then stops the user scroll action and continues with the animated scroll? So the overall appearance is as follows :
1.animating scroll
2. user tries to scroll and animation appears to stick
3. when user ends scroll animation continues
Any thoughts?
Many thanks again.
If I understand what you're trying to say, you want a continuous, slow scroll (600px per 15 seconds) which can be overruled by the user.
If this is what you want, I'd take a different approach than this one. The animation you're firing, when started, will probably just do what it's told, while the user is interacting as well, which will all get very messy.
Create an NSTimer which takes care of the slow scrolling motion, one pixel at a time. Then the user can still interact freely.
something like:
...
NSTimer *slow = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(scrollALittle:) userInfo:nil repeats:YES];
...
// maybe, somewhere:
[slow invalidate];
-(void)scrollALittle:(NSTimer*)theTimer
{
CGPoint offset = scrollview.contentOffset;
offset.y+=4; // 10 times 4 pix a second makes 600px/15 seconds.
// add check on max y?
// these checks are deliberately placed as close as possible to the contentOffset assignment
if ( scrollview.tracking ) return;
if ( scrollview.dragging ) return;
if ( scrollview.zooming ) return;
if ( scrollview.decelerating ) return;
scrollview.contentOffset = offset;
}
of course you can do a lot of things here, but this may be a starting point.