I really need help.
I am creating simple image processing app, where I load an image from the camera roll or I take a picture. I have a brightness control (slider) that will adjust the brightness of the image. The problem is slider works in real time on the simulator, but on the ipad there is small delay in response. I have tried everything, but don't seem to have any luck.
Please help. I have seen other apps where slider works smoothly without any delay. What am I doing wrong?
Thanks
I'm going to take a guess on some things that were left out of the initial question.
1) To clarify, the problem is: movement of the slider is not smooth.
2) Also, as a result of, or in combination with, this UI roughness, there is a delay in change to the image.
I'm not sure what your implementation looks like, but, it sounds like you're doing too much work and/or too much on the main thread.
So, heres what a functioning implementation might do:
- (void)sliderChanged:(UISlider *)sender
{
[self adjustImageBrightnessWithValue:sender.value;
}
- (void)adjustImageBrightnessWithValue:(CGFloat)value
{
[self cancelCurrentWork]; // Maintain a reference to an operation and cancel it
[self adjustImageBrightnessAsyncWithValue:(CGFloat)value originalImage:self.imageView.image completion:^(UIImage *finalImage)
{
self.imageView.image = finalImage;
}
}
adjustImageBrightnessAsyncWithValue takes a value, original image, and completion block. It creates an operation (via NSOperation or NSOperationQueue, probably both) and keeps track of that operation. The operation applies the algorithm to the original image in the background. Once its done, the completion block sets the final image in your image view, on the main thread.
The only things that happen on the main thread are: get the slider changed callback, canceling previous work, starting new work, and setting the final image. Everything else should happen in the background. Canceling work is an optimization for the case that the user moves the slider too fast for the image to be modified before the value changes again. Once the slider doesn't change long enough to do the modification, the result will be visible. The slider should always be smooth because nothing is blocking the main thread.
EDIT
Using an operation queue...
Declare a member variable:
NSOperationQueue *m_queue;
...
Initialize it in an init method:
m_queue = [NSOperationQueue new];
m_queue.maxConcurrentOperationCount = 1; // So it only does one brightness calculation at a time and there are no concurrency issues.
...
- (void)adjustImageBrightnessWithValue:(CGFloat)value
{
[m_queue cancelAllOperations];
[m_queue addOperationWithBlock:^
{
UIImage *adjustedImage = [mainimage brightness:value]; // Not sure where this method is coming from, but this code assumes it returns a copy of mainimage with the brightness adjusted.
dispatch_async(dispatch_get_main_queue(), ^
{
imageview.image = adjustedImage;
});
}];
}
Also, as an aside, you might take a look at GPUImage, discussed here: http://nshipster.com/gpuimage/ for numerous fast, powerful image modification techniques/APIs.
Use
slider.isContinuous = false
That's it will solve the problem.
Related
I am using the follow code that is executed on the main thread to take a screenshot of an off screen (not a subview of self.view) view that then is displayed in an UIImageView. Everything works fine in terms of functionality, however because this code is being run on an extension, there are much stricter memory bounds (I've read ~30 MB is the cap?),
UIGraphicsBeginImageContextWithOptions(CGSizeMake(self.screenshotView.frame.size.width, self.screenshotView.frame.size.height-2), YES, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
[self.screenshotView.layer renderInContext:context];
_generatedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
self.previewImageView.image = _generatedImage;
The method this code resides in gets called using performSelectorOnMainThread whenever a UIButton is pressed. A UIAlertController is also presented to handle the freezing of the UI, but if the button is pressed continuously (after dismissal of the UIAlertController), the first few times the memory usage will stay around the baseline (~15 MB), but then spike to ~30 MB and stay there until the method is called again a few seconds later and then when it is done rendering, it falls back to ~15 MB.
I'm unsure what is causing this behavior, why the memory usage doesn't stick around ~15 MB all the time, and I'm unsure as to what is the reason it spikes like that when the method is called continuously. It sounds like more that one thing is happening at once sometimes? How can I make sure this doesn't happen and only dismiss the UIAlertController when it is safe to render again without spiking memory usage.
The memory spike is not due to the renderInContext: call despite everything including instruments pointing to it, it is however due to the subviews of the captured UIView. In my case it was a faulty constraint that caused a UITextView to set its height to 2000.
To whomever has a problem like this and cannot figure it out, move on from renderInContext: and look at your subviews to make sure they are proper.
1.Could you elaborate more on why are you trying to capture screenshot on main thread? Run it async with:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
});
This might resolve the hang issue & try to execute these line of codes inside autoreleasepool which will help in reducing memory foot print.
2.Have you tried drawViewHierarchyInRect: afterScreenUpdates: It's much faster & could be more efficient and help with your memory issue.
That method renders to the context and then you can just call:
CGImageRef image = CGBitmapContextCreateImage(UIGraphicsGetCurrentContext());
but it should be executed inside an autoreleasepool. If not then memory keeps on rising.
EDIT
3.If you can prevent multiple context creation with synchronizing capture method.It might help in spiking memory:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_queue_t renderQueue = dispatch_queue_create("com.throttling.queue", NULL);
- (void) captureScreen {
if (dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW) == 0) {
dispatch_async(renderQueue, ^{
// capture
dispatch_semaphore_signal(semaphore);
});
}
}
I'm trying to extend what apple watch can do by rendering UIImage 'frames' and then setting them on my WKInterfaceImage every 500 milliseconds (2 frames per second).
My plan is to try render 8bit style games that don't need high detail or frame rates and then get arrow buttons on the watch app to direct how the game state changes.
So I have a Game class that has a 'tick' method which returns a UIImage. Each call to 'tick' will proceed the game by a single point. Think Tetris where each 'tick' of the game moves the falling blocks by one block space downwards. The game proceeds by 'ticks' until there is interaction from the user. Upon call to rotate the block, I am telling the Game class to rotate left or right when the next 'tick' happens. Here are the relevant parts of my WKInterfaceController class below.
- (void)willActivate
{
// This method is called when watch view controller is about to be visible to user
NSLog(#"%# will activate", self);
// wait 2 seconds until game 'starts'
[self performSelector:#selector(timerFired) withObject:nil afterDelay:2.];
}
- (void)timerFired
{
dispatch_async(dispatch_get_main_queue(), ^{
[self.theImage setImage:[self.game tick]];
[self performSelector:#selector(timerFired) withObject:nil afterDelay:.5];
});
}
- (IBAction)leftButtonPressed
{
self.game.futureMove = FutureMoveAntiClockWise;
}
- (IBAction)rightButtonPressed
{
self.game.futureMove = FutureMoveClockWise;
}
Now, amazingly all that code works great - for a few seconds. Then it all gets slower and slower and more and more behind (in terms of frames). After 10 seconds or so pressing the rotate left or right buttons only shows up on the rendered UIImage a few seconds later.
The watch app starts off rendering maybe 30 frames a second (if I wanted it to), and then crawls slower and slower and ultimately stops. Is there a better way to reliably setImage on a WKInterfaceImage repeatedly without it slowing down?
I'm so close!!
Thanks for you comments. After updating to Xcode 6.3 it works fine and doesn't lag but I will heed #MikeSwanson and expect Apple to reject my app :(.
I have a very annoying problem. I have a ViewController with an UIImageView in it. The UIImageView should display a slide show. The images for the are coming from NSURL, so it takes a bit of time.
- (void)viewDidLoad {
[super viewDidLoad];
[self loadImages];
}
This is how I get the Images from the NSURL. The problem I have is, that while the Images are loading I only see a black screen.
At the beginning and at the end of the -(void)loadImages method I implemented a UIActivityIndicator to display the loading time.
I already tried -(void)viewWillAppear and -(void)viewDidLayoutSubviews but nothing worked.
Thanks for help
Jannes
What you need to do is load your images asynchronously and populate them as they come in (with delegate or block callbacks, depending on the API you use). You should absolutely never run networking code, or any other potentially long-running operation, synchronously on the main thread. The reason for this is that the UI runs on the main thread, so if you block it with your own operations, the UI cannot update and your app will become unresponsive.
Whether you're using Apple's networking frameworks or something like AFNetworking (highly recommended), there are many ways to do networking asynchronously with minimal work.
You're probably downloading your images synchronously. Try wrapping it in a dispatch_async block to run it on a different thread.
Example:
-(void)loadImages {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
// download the images here
dispatch_async(dispatch_get_main_queue(), ^(void) {
// update the UI on the main thread
});
});
}
Check out the SDWebImage library as well as most of this heavy lifting is done for you.
I tried my app on my iPhone with iOS 7 and everything works perfectly except one thing. On iOS 6 versions, when I executed the following code, the loading view (with the activityindicator) appeared and disappeared at the end of the loading. On iOS 7, the view does not appear at all during the loading.
self.loadingView.alpha = 1.0;
[self performSelector:#selector(accessServices) withObject:nil afterDelay:0.0f];
AccessServices method :
- (void)accessServices {
// Getting JSON stuff
// The code is OK, I just don't copy/paste it here
self.loadingView.alpha = 0.0;
}
What happens ? Is it an iOS 7 bug ?
I wouldn't have expected the behavior of the above code to change, though I'm not surprised that it might not work the way you expect. If you do the JSON stuff on the main queue, your code will be dependent on idiosyncrasies of when the UI update takes place, rather than making it explicit.
You probably want to explicitly dispatch the JSON code to a background queue, and then dispatch the final UI update back to the main queue. The standard pattern for something like this is:
self.loadingView.alpha = 1.0;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Getting JSON stuff
// The code is OK, I just don't copy/paste it here
dispatch_async(dispatch_get_main_queue(), ^{
self.loadingView.alpha = 0.0;
});
});
You can use GCD (like above) or operation queues, or whatever. But the idea is the same, that UI updates should happen on the main queue but that anything remotely computationally expensive or slow else should happen on a background queue.
This pattern should work regardless of iOS version.
I found interesting things.. Following code doesn't show #"One" and it show #"Two" after 3 seconds delay..
I think that #"One" need to be shown and then 3 seconds delay and then #"Two" need to pop up..
Am I wrong?
self.statusLabel.text = #"One";
sleep(3);
self.statusLabel.text = #"Two";
Thanks..
If you're doing this on the main thread, that sleep(3) will block it, freezing the app for 3 seconds. Event processing, including things like repainting the UI, won't happen til that's over.
To get what you're expecting, try something like this:
[self.statusLabel setText:#"One"];
[self.statusLabel performSelector:#selector(setText:)
withObject:#"Two"
afterDelay:3.0];
Does the first change, then queues up an invocation performing the second change to happen in the future. Then returns control to the OS to do any necessary redrawing.
Your notion of how things should work is incorrect.
self.statusLabel.text = #"One";
This sets the value of the statusLabel field to "One". This does not immediately draw to the screen. Instead, the label will mark itself as needing display. At the end of the current run loop cycle, all the views marked as needed display will be drawn, and then their contents flushed to the screen.
Next you do:
sleep(3);
self.statusLabel.text = #"Two";
This blocks the main thread for 3 seconds (never returning to the run loop to do the work mentioned above), then changes the value to "Two" which marks the view again as needing display.
When it is eventually drawn, and flushed to the screen, the current value is "Two".
It is hard to give more specific advice about what you should be doing, because it isn't clear if there is a real problem you are trying to solve, or just experimenting in order to learn more about the frameworks.
But you should almost never use sleep, and you certainly shouldn't be blocking the main thread with sleep for several seconds.
You're wrong...
Think of it this way: when you execute code in a block you're telling iOS what you want to do. It only actually implements your instructions after you pass control back to the OS.
Your code blocks the main thread (which is a very bad thing to do).
What you need to do is set the label to "One" then set a timer that will fire in three seconds time. The code in the timer would set the text of the label to "Two."