I'm fairly new to coding for the iOS platform and objective C in general. I'm writing a simple iPhone app utilizing storyboard that has a tall png file displayed via UIImageView embedded within a UIScrollView and a button next to it that will play a movie.
My issue is that when the movie finishes/exits and comes back to the original screen, the scrolling within the UIScrollView does not work. I have nailed down the "cause". It occurs when I add the MPMoviePlayerViewController object.view to the self.view subview. But I'm not sure how to rectify this issue. Here is my distilled code:
.h file
#interface StuffViewController : UIViewController
#property (strong,nonatomic) IBOutlet UIImageView *imageView;
#property (strong,nonatomic) IBOutlet UIScrollView *scrollView;
-(IBAction) playMovie;
-(void) moviePlayBackDidFinish:(NSNotification*)notification;
#end
.m file
-(void) viewDidLoad {
self.imageView.image = [UIImage imageNamed:#"image.png"];
}
-(void) viewDidAppear:(BOOL)animated {
self.scrollView.contentSize = self.imageView.image.size;
}
-(void) playMovie {
NSURL *movieURL = [NSURL fileURLWithPath:[[NSBundle mainBundle]
pathForResource:#"movieTitle" ofType:#"mp4"]];
MPMoviePlayerController *moviePlayer = [[MPMoviePlayerController alloc]
initWithContentURL:movieURL];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(moviePlayBackDidFinish:)
name:MPMoviePlayerPlaybackDidFinishNotification
object:moviePlayer];
[moviePlayer setMovieSourceType:MPMovieSourceTypeFile];
[[self view] addSubview:moviePlayer.view]; //SCROLLING BREAKS HERE
[moviePlayer setFullscreen:YES];
[moviePlayer play];
}
-(void)moviePlayBackDidFinish: (NSNotification*)notification {
MPMoviePlayerController *movieDone = [notification object];
[[NSNotificationCenter defaultCenter] removeObserver:self];
[movieDone.view removeFromSuperview];
[movieDone setFullscreen:NO];
}
I have determined the culprit by commenting out sections, and like I said, the scrolling "locks" at the "[[self view] addSubview:moviePlayer.view];" line and doesn't recover until I navigate to a different view and then come back.
Any and all help on this would be greatly appreciated.
EDIT: I have discovered an interesting wrinkle that might help discover the underlying issue.
I have been using MPMoviePlayerController this whole time. However upon switching to MPMoviePlayerViewController some interesting things have been happening.
Here is the changed -(void)playMovie
-(void) playMovie {
self.scrollView.contentOffset = CGPointZero;
NSURL *url = [NSURL fileURLWithPath:[[NSBundle mainBundle]
pathForResource:totalTitle ofType:#"mp4"]];
MPMoviePlayerViewController *playerController = [[MPMoviePlayerViewController alloc] initWithContentURL:url];
[self presentMoviePlayerViewControllerAnimated:playerController];
playerController.moviePlayer.movieSourceType = MPMovieSourceTypeFile;
[playerController.moviePlayer play];
playerController = nil;
}
What is interesting is that the UIScrollView will still work, HOWEVER, if it has been scrolled down at all it will no longer be able to scroll up from where it was when the movie started. I fixed this by adding self.scrollView.contentOffset = CGPointZero at the beginning of the playMovie to tell the scrollView to move to the top (so there would be nothing above it to have to scroll back to). I assume that adding some sort of if-statement to the code in viewDidAppear that would keep scrollView.contentSize from re-executing might fix the problem of not being able to scroll back up, however I like the 'cleanness' of it starting back at the top.
One last issue though. Using MPMoviePlayerViewController like this has caused a number of interesting errors to pop up in my debugger right when MPMoviePlayerViewController *playerController = [[MPMoviePlayerViewController alloc] initWithContentURL:url]; line is executed. They are as follows:
Oct 25 10:25:51 Compy.local AppName[14590] <Error>: CGContextSaveGState: invalid context 0x0
Oct 25 10:25:51 Compy.local AppName[14590] <Error>: CGContextClipToRect: invalid context 0x0
Oct 25 10:25:51 Compy.local AppName[14590] <Error>: CGContextTranslateCTM: invalid context 0x0
Oct 25 10:25:51 Compy.local AppName[14590] <Error>: CGContextDrawShading: invalid context 0x0
Oct 25 10:25:51 Compy.local AppName[14590] <Error>: CGContextRestoreGState: invalid context 0x0
It doesn't seem to break anything. I tend to be a perfectionist when it comes to errant error statements however. I've done some research on these errors, however I haven't found anything suitable that would lend a strong hand in this situation.
Thanks for all the help so far! Once again, I would appreciate any and all help on this as well.
Are you sure that everything gets removed from the superview? Also, you might try replacing
MPMoviePlayerController *movieDone = [notification object];
with
MPMoviePlayerController *movieDone = (MPMoviewPlayerController *)[notification object];
and also add
movieDone = nil;
To make sure that your MPMoviePlayerController is completely removed from the superView, try pressing a button on your view (created before adding the MPMoviePlayerController) after the video's finished playing and see if it fires. The navigation controller must present your MPMoviewPlayerController modally or make a push. If it is modally presented, try dismissing it when the the playback's finished.
The cause of the problem was the use of 'Autolayout' in Storyboard. I'm not 100% sure why it would cause it problem after playing a movie and not before. However I fixed it by either:
A: removing Autolayout
or
B: playing with the constraint functions until it ended up working. (Changing the height of the UIImageView to 0 and changing the Equals: Default to have the lowest priority possible.)
Try setting the SetUserInteractionEnabled of self and/or self.scrollview to YES in the moviePlayBackDidFinish method.
Also, check the size of the scroll view's ContentSize property, to check if the size is bigger than your actual screen size to see if that's causing the scroll view's functionality to break.
Related
I have a video that auto plays at launch. When the short clip is finished it shows a black screen. I would like to dismiss the subview to show an image or auto load another controller??
Below is my code:
(void)viewDidLoad
{
{
NSURL *url = [NSURL fileURLWithPath:[[NSBundle mainBundle]
pathForResource:#"cover" ofType:#"mp4"]];
MPMoviePlayerController *player = [[MPMoviePlayerController alloc]
initWithContentURL:url];
player.movieSourceType = MPMovieSourceTypeFile;
[player setControlStyle:MPMovieControlStyleNone];
player.view.frame = CGRectMake(0, 0, 768, 960);
[self.view addSubview:player.view];
[player play];
player = nil;
}
Thanks for any help..i'm a rookie at this.
I figured out a lot of this a couple of weeks ago. Check out the notifications available. http://developer.apple.com/library/ios/#documentation/mediaplayer/reference/MPMoviePlayerController_Class/Reference/Reference.html
Add something like this to viewDidLoad:
// Remove the movie player view controller from the "playback did finish" notification observers
[[NSNotificationCenter defaultCenter] removeObserver:_moviePlayer
name:MPMoviePlayerPlaybackDidFinishNotification
object:_moviePlayer];
// Register this class as an observer instead
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(movieFinishedCallback:)
name:MPMoviePlayerPlaybackDidFinishNotification
object:_moviePlayer];
Now you have a method where you can re-add the view, or a thumbnail, or whatever:
- (void)movieFinishedCallback:(NSNotification*)aNotification
{
// Obtain the reason why the movie playback finished
NSNumber *finishReason = [aNotification userInfo][MPMoviePlayerPlaybackDidFinishReasonUserInfoKey];
if ([finishReason intValue] == MPMovieFinishReasonPlaybackEnded) {
[self.view addSubview:self.moviePlayer.view];
}
(This code was adapted on the fly from my project, may need thinking to adapt!)
The MPMoviePlayerController has a lot of notifications that get fired when the movie is playing, is paused, is stopped, is finished, etc. You can add your code to those methods to get very good control of your presentation.
In my case, it took about a day of research and hacking (and maybe another half day of cleanup and tuning), but I managed to get a very nice play/pause transparent button, with a "play icon" image overlay when paused or stopped, all loading or unloading based on the player state. It's a simple custom player control that does exactly what I want. Totally doable, just start with one player state, get what you want, and move onto the next state.
I'm new to Xcode. This is probably a very simple question to you, but I wonder if anyone can help me.
What I want to do is to load an image in a customised UIImageView, when clicking the image, the imageview will change to another image. When I run the simulator, there are no error messages, but the second image does not load after clicking.
I've declared a UIImageView subclass dragView. In the dragView.m, I have such codes:
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self changePicture];
}
-(void) changePicture {
NSString *path = [[NSHomeDirectory() stringByAppendingPathComponent:#"Documents"] stringByAppendingPathComponent:#"Red Flower.png"];
self.image = [UIImage imageWithContentsOfFile:path];
}
By the way, I have enabled the interaction. Many thanks!
You probably need to tell the OS to update the view by calling [self setNeedsDisplay:YES].
I have a detail view, and when viewdidload in detailviewcontroller, MPMoviePlayerController allocs and plays an audio, but even if I navigate backto main table, audio is still being played.
How can I stop MPMovieplayercontroller when I navigate back to main table ? This is my MPMoviePlayerController code:
.h
MPMoviePlayerController *player;
.m
- (void)viewDidLoad
{
[super viewDidLoad];
//Get the Movie
NSURL *movieURL = [NSURL URLWithString:#"some link"];
player = [[MPMoviePlayerController alloc] initWithContentURL:movieURL];
//Place it in subview, else it won’t work
player.view.frame = CGRectMake(20, 20, 280, 25);
player.backgroundView.backgroundColor = [UIColor clearColor];
[self.view addSubview:player.view];
// Play the movie.
[player play];
}
I even added following code into viewdidunload method, but didn't work.
- (void)viewDidUnload {
[player stop];
player.initialPlaybackTime = -1;
[player release];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
What do you guys suggest ?
Thanks in advance,
I liked the user experience of viewDidDisappear better than viewWillDisappear. The animation starts and the movie stops after - the audio flows better for me this way.
-(void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[_moviePlayer stop];
}
I am having a similar issue. I am unable to use "viewDidDisappear" or "viewWillDisappear" because I have a "config" type view that can be opened while the content is playing, and it will trigger those two methods.
EDIT: Found that viewDidUnload and viewWillUnload are not getting called any more (I'm currently on an iOS 6+ device)...
From the documentation:
Availability: iOS (3.0 and later) Deprecated: Views are no longer
purged under low-memory conditions and so this method is never called.
I just created a simple function called unload, and inside the function, set any objects I needed to = nil (I'm using ARC). At the time that I make the call to remove the view, I call the unload function as well. Hope it helps someone.
I'm trying to make a simple menu bar only application on xcode 4. Everything actually works, but what I don't understand is that the icon is appearing twice in the menubar. One of the two icons actually works and gives the drop down menu with the working buttons, the other just changes to the highlighted icon images while clicked and goes back when released, without doing anything, not even the drop down menu appears.
This is the code I found and tested out:
- (void) awakeFromNib{
//Create the NSStatusBar and set its length
statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain];
//Used to detect where the files are
NSBundle *bundle = [NSBundle mainBundle];
//Allocates and loads the images into the application which will be used for the NSStatusItem
statusImage = [[NSImage alloc] initWithContentsOfFile:[bundle pathForResource:#"icon" ofType:#"png"]];
statusHighlightImage = [[NSImage alloc] initWithContentsOfFile:[bundle pathForResource:#"icon-alt" ofType:#"png"]];
//Sets the images in the NSStatusItem
[statusItem setImage:statusImage];
[statusItem setAlternateImage:statusHighlightImage];
//Tells the NSStatusItem what menu to load
[statusItem setMenu:statusMenu];
//Sets the mouse over text
[statusItem setToolTip:#"My Custom Menu Item"];
//Enables highlighting
[statusItem setHighlightMode:YES];
then release the images
- (void) dealloc {
//Releases the 2 images we loaded into memory
[statusImage release];
[statusHighlightImage release];
[super dealloc];
and the header file:
#interface MenuletApp : NSObject <NSApplicationDelegate> {
NSWindow *window;
IBOutlet NSMenu *statusMenu;
NSStatusItem *statusItem;
NSImage *statusImage;
NSImage *statusHighlightImage;
with an IBAction to log Hello World when one of the items is clicked, and to terminate when the other is clicked.
I used a tutorial meant for XCode 3, so it might be that one of the steps is done differently, but looking at the code I have no idea where the second status item is getting created.
Thanks for the help.
Is it possible that -awakeFromNib is getting called twice? (Try putting a log message in there). Perhaps you have two objects of this class in your xib file?
Also, I'd recommend moving this to -applicationDidFinishLaunching:.
I'm working on NSStatusItem. I've managed to use the setImage and setAlternateImage to work.
When user selects something, it takes a while for it to accomplish whatever it is doing. While it is doing something, I tried changing from the usual Image to a spinner. The way that I am doing it now is that I created a view, set the NSProgressIndicator to it, and then use
[statusItem setView: viewWithSpinner];
It seems to work until I try to remove it and display the original image. The only way I can hide it is to do
[statusItem setView: nil];
but that breaks everything, the original images don't come back. I guess cause there's no more view. I can't seem to save the original view before setting the viewWithSpinner.
Can someone advise me of a way of accomplishing this?
So...
NSStatusItem *myStatusItem;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[myStatusItem setImage:statusImage];
[myStatusItem setHighlightMode:YES];
[myStatusItem setAlternateImage:statusImageSel];
[myStatusItem setMenu:myStatusMenu];
etc...
[self createSpinner];
}
-(void)createSpinner
{
//to overcome the white border problem
NSView *progressIndicatorHolder = [[NSView alloc] init];
NSProgressIndicator *progressIndicator = [[NSProgressIndicator alloc] init];
[progressIndicator setBezeled: NO];
[progressIndicator setStyle: NSProgressIndicatorSpinningStyle];
[progressIndicator setControlSize: NSSmallControlSize];
[progressIndicator sizeToFit];
[progressIndicator setUsesThreadedAnimation:YES];
[progressIndicatorHolder addSubview:progressIndicator];
[progressIndicator startAnimation:self];
//for testing purposes
[[myStatusItem view] addSubview:progressIndicatorHolder];
spinnerView = progressIndicatorHolder;
}
I suggest save old view using [statusItem view] before setting to any other view. Before coming back to normal menu, set old saved view to statusItem using setView method.
If you're looking to just hide the NSStatusItem view, just call [yourStatusItem setLength:0].