I've encountered a very interesting example, that I am so curios to reproduce.
There is a great app that I love a lot using:
https://itunes.apple.com/en/app/oldbooth/id298007500?mt=8
I an not advertising as I am not the author.
Now they have a interesting point, when from a table view, by selecting a thumb, a ViewController with a video session is loaded.
What I've noticed:
1) The loading of the video session screen is really fast, almost no waiting.
2) No UIImagePicker shutter animation present
3) The VC appearance animations looks like "pushVC", I mean it is horizontal
4)the view with preview is firstly white, then with an alpha animation it goes to 0 alpha.
I have some experience using AVFoundation video capturing, but it is not as quick! It is incredible for me, and I see that this level of speed is possible, ofcourse I want the same.. Please could you please advice how it is possible to achieve such level of performance?
I am trying the following:
In parent VC, in didLoad:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:[NSBundle mainBundle]];
self.photoVC = [storyboard instantiateViewControllerWithIdentifier:#"PhotoCreationVC"];
dispatch_async(queue, ^{
[self.photoVC configureFoundation];
});
The method does the following:
-(void) configureFoundation{
NSError * error;
_captureManager = [[AVCamDemoCaptureManager alloc] init];
if ([_captureManager setupSessionWithPreset:AVCaptureSessionPresetHigh error:&error]) {
[self setCaptureManager:_captureManager];
_sessionEstablished = YES;
} else {
[Utils showMessage:[error localizedDescription] withTitle:#"Input Device Init Failed"];
}
}
and when the VC runs didLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
if (_sessionEstablished) {
AVCaptureVideoPreviewLayer *captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:[_captureManager session]];
UIView *view = self.previewView;
CALayer *viewLayer = view.layer;
CGRect bounds = view.bounds;
[captureVideoPreviewLayer setFrame:bounds];
if ([captureVideoPreviewLayer isOrientationSupported]) {
[captureVideoPreviewLayer setOrientation:AVCaptureVideoOrientationPortrait];
}
[captureVideoPreviewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
self.captureVideoPreviewLayer = captureVideoPreviewLayer;
if ([[_captureManager session] isRunning]) {
[_captureManager setDelegate:self];
NSUInteger cameraCount = [_captureManager cameraCount];
if (cameraCount < 1) {
} else if (cameraCount < 2) {
}
if (cameraCount < 1) {
}
[viewLayer insertSublayer:captureVideoPreviewLayer below:[[viewLayer sublayers] objectAtIndex:0]];
} else {
[Utils showMessage:#"Failed to start session." withTitle:#"Error"];
}
}
}
So what happens:
in the iTunes app it firstly loads the screen, then releases the alpha of the white view (i guess it is just cover view, which starts to lose alfa after a delay).
In my case, it still thinks a while before loading the VC. The time (1.5 seconds) is the same, but the waiting is not so friendly.
If i put all the loading code in didAppear, it loads immediately, but the loads, so the total time between tap and ready-video increases to 2.5 secs, which is not good.
If i put the loading code in willAppear, it works the same as in the first situation, when it first thinks, the loads (total 1.5 secs)...
PS I use AVCamDemoCaptureManager sample code from Apple, simplified, without any audio and movie output. just video input device and still image capturing.
Guys, please, can you give me some performance advice? how it is possible to achieve some high performance in loading a camera session?
Any help will be highly appreciated!
Related
Maybe this is purely simulator related. I have not tried it on an actual device yet.
I'm on the latest greatest MacBook with a 1TB flash drive, and 95% free processor, and less than full memory consumption.
I have a UIPopoverController with 4 items in it, sized to those items.
There's nothing complicated or multi-threaded or long running in any way associated in the UIPopoverController in question.
I've set the appear and dismiss animation at 0, yet when I tap on an item in the list, there seems to be an random indeterminate delay between 0 and .4 seconds in the popover disappearing. Of course the 0 is expected, but the times when it's nearly a half second is very noticeably longer and disconcerting.
Any idea what may be causing that?
Code that shows the popover...
-(IBAction)theLandImpsButtonPressed:(UIButton *)sender
{
iRpNameValuePopover *thePopoverContent = [[iRpNameValuePopover alloc] init];
thePopoverContent.theTableValues = [self getLandImpsChoicesList];
impsLandPopover = [[UIPopoverController alloc] initWithContentViewController:thePopoverContent];
thePopoverContent.thePopoverController = impsLandPopover;
impsLandPopover.popoverContentSize = [iRpUIHelper sizeForPopoverThatHasTitle:NO andListContent:thePopoverContent.theTableValues];
impsLandPopover.delegate = self;
[impsLandPopover presentPopoverFromRect:self.theLandImpsButton.bounds inView:self.theLandImpsButton permittedArrowDirections:UIPopoverArrowDirectionAny animated:NO];
}
Code that dismisses the popover...
BTW, there is no evaluation time incurred here [self userChoiceIsValid] because it simply returns YES right now.
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
_theChosenNameValueItem = [self.theTableValues objectAtIndex:indexPath.row];
[self acceptUserChoiceAndClose];
}
// This contentViewController is encapsulated INSIDE a UIPopoverViewController, and this class cannot itself
// close the popover which contains it, hence the need for the reference to the popover controller
// It is the popover's delegate... the one that created the popover, that is able to close it.
-(void)acceptUserChoiceAndClose
{
_theUserChoseAValue = NO; // Start by assuming they didn't chose a valid value.
if ([self userChoiceIsValid])
{
// Set variable that indicates the user chose a value which can be saved to core data, and/or presented on screen.
_theUserChoseAValue = YES;
// Close the popover.
[_thePopoverController dismissPopoverAnimated:NO];
// Notify the class that presented the popover that the popover has been dismissed.
// It will still be available to the dismissal method where code can retrieve the user's choice, and set the popover to nil.
if (_thePopoverController.delegate && [_thePopoverController.delegate respondsToSelector:#selector(popoverControllerDidDismissPopover:)])
{
[_thePopoverController.delegate popoverControllerDidDismissPopover:_thePopoverController];
}
}
else
{
[self showValidationFailureMessageToUser];
}
}
Dismissing the viewController in main thread will solve the issue.
dispatch_async(dispatch_get_main_queue(), ^{
[self dismissViewControllerAnimated:YES completion:nil];
});
I would check it out in the profiler and see what the time is being spent on.
There's a good tutorial here.
UIPopoverPresentationController *popOverView;
//////
Dismiss it...
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
[popOverView.presentedViewController dismissViewControllerAnimated:NO completion:nil];
popOverView = nil;
});
});
When i try to load camera from my code, camera preview is black. If I wait for 10-20 seconds it will show real camera preview. I found several questions and some of them suggest that running some other code in background should be the reason for this. However I don't have any code running in background.
How should I fix this?
This is my code where I run camera
UIImagePickerController *photoPicker = [[UIImagePickerController alloc] init];
photoPicker.delegate = self;
photoPicker.sourceType = UIImagePickerControllerSourceTypeCamera;
[self presentViewController:photoPicker animated:YES completion:NULL];
About 5 months ago my team discovered a memory leak with UIImageViewController in iOS7. Each instantiation slowed down the app exponentially (i.e. first alloc-init had a 1 second delay, second had a 2 second delay, third had a 5 second delay). Eventually, we were having 30-60 delays (similar to what you're experiencing).
We resolved the issue by subclassing UIImagePickerController and making it a Singleton. That way it was only ever initialized once. Now our delay is minimal and we avoid the leak. If subclassing isn't an option, try a class property in your viewController and just lazy load it like so.
-(UIImagePickerController *)imagePicker{
if(!_imagePicker){
_imagePicker = [[UIImagePickerController alloc]init];
_imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
}
return _imagePicker;
}
Then you can just call it later like:
[self presentViewController:self.imagePicker animated:YES completion:nil];
Had this myself - it happens if something is running on the main dispatch thread - are you resizing images by any chance?
It puts the preview onto the main thread and if something is using it, you get a black screen. It's a bug and the workaround is to either take over the main thread or to disable the photo picker until the queue is free
This Should work for you:
- (void)cameraViewPickerController:(UIImagePickerController *)picker
{
[self startCameraControllerFromViewController: picker
usingDelegate: self];
}
- (BOOL) startCameraControllerFromViewController: (UIViewController*) controller
usingDelegate: (id <UIImagePickerControllerDelegate,
UINavigationControllerDelegate>) delegate {
if (([UIImagePickerController isSourceTypeAvailable:
UIImagePickerControllerSourceTypeCamera] == NO)
|| (delegate == nil)
|| (controller == nil))
return NO;
UIImagePickerController *cameraUI = [[UIImagePickerController alloc] init];
cameraUI.sourceType = UIImagePickerControllerSourceTypeCamera;
// Displays a control that allows the user to choose movie capture
cameraUI.mediaTypes = [[NSArray alloc] initWithObjects: (NSString *) kUTTypeImage, (NSString *) kUTTypeMovie,nil];
// Hides the controls for moving & scaling pictures, or for
// trimming movies. To instead show the controls, use YES.
cameraUI.allowsEditing = NO;
cameraUI.delegate = delegate;
[controller presentViewController:cameraUI animated:YES completion:nil];
return YES;
}
So we are making a game that has a normal UIView title screen, and when the user presses the "Start Game" button, we attempt to put up the loading screen, remove the title view, we initialize the game view and add it to the superview (window in our case) underneath the loading screen.
Unfortunately, the end result is that the UI is blocked for a few seconds upon touching the "Start Game" button, and our loading screen blips on the screen for a millisecond before being kicked off for the loaded game view. We even added logging, and the logging messages appear in console while the UI is blocked.
Here is the method in question:
-(void)switchToGame{
NSLog(#"adding loading");
loadingScreen = [[UIImageView alloc] initWithImage:
[UIImage imageNamed:#"loading_screen.jpg"]];
[self.window addSubview:loadingScreen];
NSLog(#"removing titlescreen");
[titleView.view removeFromSuperview];
self.titleView = nil;
if(self.gameView == nil){
gmViewController *g = [[gmViewController alloc]
initWithNibName:#"gmViewController"
bundle:nil];
self.gameView = g;
[gameView setIsLoading:YES];
self.gameView.parent = self;
[g release];
}
NSLog(#"inserting gameview");
[self.window insertSubview:gameView.view belowSubview:loadingScreen];
[self.window setRootViewController:gameView];
[self setGameIsActive:YES];
[gameView startAnimation];
//remove the loading screen
NSLog(#"killing loading");
[loadingScreen removeFromSuperview];
[loadingScreen release];
[gameView setIsLoading:NO];
}
Forgot about this question.
We solved it by moving the loading screen add to it's own function:
- (void)addLoadingScreen
{
loadingScreen = [[UIImageView alloc] initWithImage:
[UIImage imageNamed:#"loading_screen.jpg"]];
[self.window addSubview:loadingScreen];
}
And then executing it on its own thread:
[NSThread detachNewThreadSelector:#selector(addLoadingScreen)];
I want to implement startup loader in my app. It should be like this: after startup splash screen, user will watch simple animataion and in meanwhile app preload sound effects, background music, sprite images, spritesheets and so on. Current implementation:
- (id)init {
if((self = [super init])) {
// Some other setup ...
CGRect rect;
rect = waveSprite.textureRect;
waveInitialTexRectOrigin = rect.origin;
rect.size.width = 91;
waveSprite.textureRect = rect;
assetFilenames = [[NSArray alloc] initWithObjects:
// images
#"background.png",
// spritesheets
#"sprites.plist",
// fonts
#"main.png",
// sound effects
#"button.wav",
nil];
assetCounter = 0;
[self loadAsset];
}
return self;
}
- (void)update:(ccTime)dt {
CGRect rect;
rect = waveSprite.textureRect;
rect.origin.x += dt*kLoaderWaveSpeed;
while (rect.origin.x > waveInitialTexRectOrigin.x + kLoaderWavePeriod) {
rect.origin.x -= kLoaderWavePeriod;
}
waveSprite.textureRect = rect;
}
#pragma mark Private
- (void)loadAsset {
// CCLOG(#"loadAsset");
NSString *filename = [assetFilenames objectAtIndex:assetCounter];
CCLOG(#"loading %#", filename);
NSString *ext = [filename pathExtension];
if ([ext doesMatchRegStringExp:#"[png|jpg]"]) {
[[CCTextureCache sharedTextureCache] addImage:filename];
} else if ([ext isEqualToString:#"plist"]) {
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:filename];
} else if ([ext doesMatchRegStringExp:#"[caf|wav|mp3]"]) {
[[SimpleAudioEngine sharedEngine] preloadEffect:filename];
}
assetCounter++;
if (assetCounter < [assetFilenames count]) {
[self performSelector:#selector(loadAsset) withObject:self afterDelay:0.1f];
} else {
[self performSelector:#selector(loadingComplete) withObject:self afterDelay:0.2];
}
But the animation is SO abrupt.
UPD I've already tried
[self performSelectorInBackground: withObject:]
but it didn't seem to work (hung on loading first asset). Maybe I should try better in this direction.
UPD2 Smooth = not abrupt, without delays and flicker. fps doesn't matter, 20 fps quite OK
I'm not experienced with CC2D, but here's what I used in my ViewController to show a smooth animated loading indicator:
//Set up and run your loading scene/indicator here
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
//Load your scene
dispatch_async( dispatch_get_main_queue(), ^{
//This fires when the loading is complete
//Show menu and destroy the loader scene/indicator here
});
});
I guess it's worth a try.
You should not be using performSelectorInBackground anymore, it is not deprecated but it might be soon, with GCD now in the picture.
What I would try is run the animation in the main thread since it is the only one that can do UI updates. And spawn one or more threads using GCD to load all your assets.
I would use concurrent dispatch queues to load everything in parallel and simply dismiss the animation when this is done.
You can get complete information about concurrent dispatch queues here:
Concurrency Programming Guide
Hope that helps.
That is because you are doing everything on the same thread. Use a separate thread for the loader.
Hope this helps.
Cheers!
EDIT : Take a look at this.
All,
I am attempting to load a set of sounds asynchronously when I load a UIViewController. At about the same time, I am (occasionally) also placing a UIView on the top of my ViewController's hierarchy to present a help overlay. When I do this, the app crashes with a bad exec. If the view is not added, the app does not crash. My ViewController looks something like this:
- (void)viewDidLoad
{
[super viewDidLoad];
__soundHelper = [[SoundHelper alloc] initWithSounds];
// Other stuff
}
- (void)viewDidAppear:(BOOL)animated
{
// ****** Set up the Help Screen
self.coachMarkView = [[FHSCoachMarkView alloc] initWithImageName:#"help_GradingVC"
coveringView:self.view
withOpacity:0.9
dismissOnTap:YES
withDelegate:self];
[self.coachMarkView showCoachMarkView];
[super viewDidAppear:animated];
}
The main asynchronous loading method of SoundHelper (called from 'initWithSounds') looks like this:
// Helper method that loads sounds as needed
- (void)loadSounds {
// Run this loading code in a separate thread
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
NSBlockOperation *loadSoundsOp = [NSBlockOperation blockOperationWithBlock:^{
// Find all sound files (*.caf) in resource bundles
__soundCache = [[NSMutableDictionary alloc]initWithCapacity:0];
NSString * sndFileName;
NSArray *soundFiles = [[NSBundle mainBundle] pathsForResourcesOfType:STR_SOUND_EXT inDirectory:nil];
// Loop through all of the sounds found
for (NSString * soundFileNamePath in soundFiles) {
// Add the sound file to the dictionary
sndFileName = [[soundFileNamePath lastPathComponent] lowercaseString];
[__soundCache setObject:[self soundPath:soundFileNamePath] forKey:sndFileName];
}
// From: https://stackoverflow.com/questions/7334647/nsoperationqueue-and-uitableview-release-is-crashing-my-app
[self performSelectorOnMainThread:#selector(description) withObject:nil waitUntilDone:NO];
}];
[operationQueue addOperation:loadSoundsOp];
}
The crash seems to occur when the block exits. The init of FHSCoachMarkView looks like this:
- (FHSCoachMarkView *)initWithImageName:(NSString *) imageName
coveringView:(UIView *) view
withOpacity:(CGFloat) opacity
dismissOnTap:(BOOL) dismissOnTap
withDelegate:(id<FHSCoachMarkViewDelegate>) delegateID
{
// Reset Viewed Coach Marks if User Setting is set to show them
[self resetSettings];
__coveringView = view;
self = [super initWithFrame:__coveringView.frame];
if (self) {
// Record the string for later reference
__coachMarkName = [NSString stringWithString:imageName];
self.delegate = delegateID;
UIImage * image = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:imageName ofType:#"png"]];
// ****** Configure the View Hierarchy
UIImageView *imgView = [[UIImageView alloc] initWithImage:image];
[self addSubview:imgView];
[__coveringView.superview insertSubview:self aboveSubview:__coveringView];
// ****** Configure the View Hierarchy with the proper opacity
__coachMarkViewOpacity = opacity;
self.hidden = YES;
self.opaque = NO;
self.alpha = __coachMarkViewOpacity;
imgView.hidden = NO;
imgView.opaque = NO;
imgView.alpha = __coachMarkViewOpacity;
// ****** Configure whether the coachMark can be dismissed when it's body is tapped
__dismissOnTap = dismissOnTap;
// If it is dismissable, set up a gesture recognizer
if (__dismissOnTap) {
UITapGestureRecognizer * tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(coachMarkWasTapped:)];
[self addGestureRecognizer:tapGesture];
}
}
return self;
}
I have tried invoking the asynchronous block using both NSBlockOperation and dispatch_async and both have had the same results. Additionally, I've removed the aysnch call altogether and loaded the sounds on the main thread. That works fine. I also tried the solution suggested by #Jason in: NSOperationQueue and UITableView release is crashing my app but the same thing happened there too.
Is this actually an issue with the view being added in FHSCoachMarkView, or is it possibly related to the fact that both access mainBundle? I'm a bit new to asynch coding in iOS, so I'm at a bit of a loss. Any help would be appreciated!
Thanks,
Scott
I figured this out: I had set up a listener on the SoundHelper object (NSUserDefaultsDidChangeNotification) that listened for when NSUserDefaults were changed, and loaded the sounds if the user defaults indicated so. The FHSCoachMarkView was also making changes to NSUserDefaults. In the SoundHelper, I was not properly checking which defaults were being changed, so the asynch sound loading method was being called each time a change was made. So multiple threads were attempting to modify the __soundCache instance variable. it didn't seem to like that.
Question: Is this the correct way to answer your own question? Or should I have just added a comment to the question it self?
Thanks.