Odd behavior in NSViewController - objective-c

I've started a tiny project to get my head around NSViewControllers.
I have an AppController that handles a NSOpenPanel. Once I get a URL to a movie file, I pass it to a NSViewController subclass (NNMovieViewController). This is how I do it:
-(void)openMovieWithURL:(NSURL *)url {
NSError *error;
movie = [[QTMovie alloc] initWithURL:url error:&error];
[startButton setEnabled:YES];
[movieView setMovie:movie];
NSLog(#"button: %#", [startButton isEnabled]?#"YES":#"NO");
// logs "NO"
NSLog(#"movie: %#", movie);
// logs the correct movie object
NSLog(#"movieView: %#", [movieView movie]);
// logs "(null)"
}
The header file looks like this:
#import <Cocoa/Cocoa.h>
#import <QTKit/QTKit.h>
#interface NNMovieViewController : NSViewController {
QTMovie *movie;
BOOL playing;
IBOutlet QTMovieView *movieView;
IBOutlet NSButton *startButton;
}
-(IBAction)clickStart:(id)sender;
-(void)openMovieWithURL:(NSURL*)url;
#end
What am I missing? I re-did the whole thing in a project without a NSViewController and it just worked...
UPDATE
After I received the comments from Kreiri and Parag Bafna I tinkered a little bit more and found out that at the time I call [movieViewController openMovieWithURL:url]; inside my AppController the Outlets are not hooked up yet.
This is my AppController implementation:
#import "AppController.h"
#implementation AppController
#synthesize movieViewController;
- (void)awakeFromNib {
movieViewController = [[NNMovieViewController alloc] initWithNibName:#"NNMovieViewController" bundle:nil];
NSView *viewControllerView = [movieViewController view];
[view addSubview:viewControllerView];
}
- (IBAction)clickOpen:(id)sender {
NSOpenPanel *dialog = [NSOpenPanel openPanel];
[dialog setCanChooseFiles:TRUE];
[dialog setCanChooseDirectories:FALSE];
[dialog setAllowsMultipleSelection:FALSE];
[dialog setAllowedFileTypes:[QTMovie movieFileTypes:0]];
if ([dialog runModal] == NSOKButton) {
NSURL *movieFileURL = [[dialog URLs] objectAtIndex:0];
[self openMovie:movieFileURL];
}
}
- (void)openMovie:(NSURL *)url {
NSLog(#"startButton: %#", [movieViewController movieView]);
// logs "null"
NSLog(#"startButton: %#", [movieViewController startButton]);
// logs "null"
NSLog(#"---------------------------------");
[movieViewController openMovieWithURL:url];
}
#end

Yes, silly me. In Interface Builder I hooked up my controls with the wrong object. I should have used File's Owner but instead I dragged in an NSObject and set its class to NNMovieViewController and connected the widgets to it.

Related

Identical Functions, both get executed, no Sound in one

I'v googled the problem but couldn't find a similar case.
I've used this tutorial to play the testSound.mp3 - File when hitting a button on the iPad-Simulator:
http://mobileorchard.com/easy-audio-playback-with-avaudioplayer/
It works that way (it plays the sound), if the playSound-Method is in my ViewController.m, but not in my Sound.m (which has a identical method).
The Code gets executed (NSLog says: "Sound.m playSound executed"), but there is no sound at all.
I'd really appreciate some help here, guess I'm totally stuck... :(
Best regards,
- Teapot
// ViewController.h
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#import "Sound.h"
#interface ViewController : UIViewController {
AVAudioPlayer *audioPlayer;
}
- (IBAction)pressButton:(id)sender;
- (void)playSound: (NSString*) soundFile volume : (NSInteger) volume repeats : (NSInteger) repeats;
#end
// ViewController.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading thea view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)pressButton:(id)sender {
NSLog (#"Method: pressButton");
[self playSound: #"testSound.mp3" volume: 2 repeats: 2 url : url]; //It works!
Sound *tempSound = [[Sound alloc] init];
[tempSound playSound: #"testSound.mp3" volume: 2 repeats: 2]; // Doesn't work. -> Says "Sound.m playSound executed", but there is no Sound.
}
- (void)playSound: (NSString*) soundFile volume : (NSInteger) volume repeats : (NSInteger) repeats {
NSLog(#"ViewControler playSound");
NSError *error;
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
audioPlayer.numberOfLoops = -1;
if (audioPlayer == nil){
NSLog([error description]);
NSLog(#"ViewController.m playSound NOT executed");
}
else{
[audioPlayer play];
NSLog(#"ViewController.m playSound executed");
}
}
#end
// Sound.h
#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
#interface Sound : NSObject {
AVAudioPlayer *audioPlayer;
}
- (void) playSound: (NSString*) soundFile volume : (NSInteger) volume repeats : (NSInteger) repeats;
#end
// Sound.m
#import "Sound.h"
#implementation Sound
- (void)playSound: (NSString*) soundFile volume : (NSInteger) volume repeats : (NSInteger) repeats {
NSLog(#"Sound playSound");
NSError *error;
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
audioPlayer.numberOfLoops = -1;
if (audioPlayer == nil){
NSLog([error description]);
NSLog(#"Sound.m playSound NOT executed");
}
else{
[audioPlayer play];
NSLog(#"Sound.m playSound executed");
}
}
#end
There are some inconsistencies in your code: playSound: has an NSString parameter, but AVAudioPlayer inside that method uses a NSURL. Then you set numberOfLoops = -1 (which means infinite repetition) instead of numberOfLoops = repeat.
But the main problem is that here (assuming that you compile with "Automatic Reference Counting")
Sound *tempSound = [[Sound alloc] init];
[tempSound playSound: #"testSound.mp3" volume: 2 repeats: 2];
the tempSound object is deallocated when the pressButton: is left, because no strong references to that object exist anymore.
If you add an instance variable (or property) sound to the view controller class, and assign to that
sound = [[Sound alloc] init];
[sound playSound: #"testSound.mp3" volume: 2 repeats: 2];
then it should work as expected.
Alternatively, you could prevent the Sound object from being deallocated too early by maintaining a "self reference" inside the object, which is removed only when the sound has finished playing:
#interface Sound () <AVAudioPlayerDelegate>
#property(strong, nonatomic) AVAudioPlayer *audioPlayer;
#property(strong, nonatomic) Sound *selfRef;
#end
#implementation Sound
- (void)playSound:(NSString *)soundFile volume:(NSInteger)volume repeats:(NSInteger)repeats
{
NSLog(#"Sound playSound");
NSURL *soundURL = [[NSBundle mainBundle] URLForResource:soundFile withExtension:nil];
NSError *error;
self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:soundURL error:&error];
if (self.audioPlayer == nil) {
NSLog(#"%#", [error description]);
NSLog(#"Sound.m playSound NOT executed");
} else{
self.audioPlayer.numberOfLoops = repeats;
self.audioPlayer.delegate = self;
[self.audioPlayer play];
self.selfRef = self; // self reference to avoid deallocation
NSLog(#"Sound.m playSound executed");
}
}
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
{
self.selfRef = nil; // remove self reference
}
#end
Of course, you shouldn't do this with "infinite repetition"!

How Do I Update UIWebView After viewDidLoad?

This is my first iOS app, so I am probably missing something very simple. Please be kind. I have been tearing my hair out and I could really use some help.
Overview Of App
Basically, this is a single page application that just loads a UIWebView. I have an external accessory (bluetooth barcode scanner) that I connect and basically what I want to do is when the the app receives a scan, I want to call a method in my ViewController and update the UIWebView accordingly.
What Is Working
I am able to connect the scanner, load the first view, which loads the initial webpage, scan a barcode and call the method in my controller.
My Problem
I can't seem to figure out how to update the UIWebView from the method in my controller. It logs the url string to my debugger area, but never actually updates the webview. I am pretty sure I have some delegation wrong or something with my webview instance. There must be some glue code here that I am missing.
My Code HelloWorldViewController.h
#import <UIKit/UIKit.h>
#import "KScan.h"
#interface HelloWorldViewController : UIViewController <UIWebViewDelegate> {
IBOutlet UIWebView *page;
IBOutlet UILabel *myLabel;
Boolean IsFirstTime;
KScan *kscan;
}
- (void)setFirstTime;
- (void)DisplayConnectionStatus;
- (void)DisplayMessage:(char *)Message;
- (void)newBarcodeScanned:(NSString *)barcode;
- (void)loadBarcodePage:(NSString *)barcode;
#property (nonatomic, retain) KScan *kscan;
#property (nonatomic, retain) UIWebView *page;
#property (nonatomic, retain) UILabel *myLabel;
#end
My Code HelloWorldViewController.m
#import "HelloWorldViewController.h"
#import "common.h"
#implementation HelloWorldViewController
#synthesize myLabel;
#synthesize page;
#synthesize kscan;
- (void)setFirstTime
{
IsFirstTime = true;
}
- (void)viewDidLoad
{
self.kscan = [[KScan alloc] init];
[super viewDidLoad];
page.scrollView.bounces = NO;
//page.delegate = self;
[page loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://192.168.0.187:3000"]]];
}
- (void) newBarcodeScanned:(NSString *)barcode
{
NSLog(#"%s[%#]",__FUNCTION__, barcode);
[self loadBarcodePage:barcode];
}
- (void)loadBarcodePage:(NSString *)barcode
{
NSLog(#"%s",__FUNCTION__);
NSString *url = [[NSString alloc] initWithFormat:#"http://www.google.com/%#", barcode];
NSLog(#"%#", url);
[page loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:url]]];
}
- (void)viewDidUnload
{
[myLabel release];
myLabel = nil;
[super viewDidUnload];
// Release any retained subviews of the main view.
}
- (BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)interfaceOrientation
{
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
} else {
return YES;
}
}
- (void)dealloc {
[page release];
[kscan release];
[myLabel release];
[super dealloc];
}
#end
Basically, I am just trying to load google.com into my page webview when scanning a barcode. My log statements are being logged with the correct URL, but this line of code doesn't work.
[page loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:url]]];
I am not getting any errors and my xCode debugging skills are not the greatest.
Any help would be greatly appreciated!
It looks like your webview is never allocated, or added to your main view. You are probably talking to a nil instance.
Unless your web view comes from a XIB file (which I doubt since it is not declared as an IBOutlet in your heder file) try adding something like this to your viewDidLoad:
self.page = [[UIWebView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:self.page];

Objective c: Protocol + Delegate to pass data back from Login form in a Modal View to an tab bar controller view

I'm developing a tab bar based app for iPhone.
The flow is the following: when the app runs, I throw up the modal view with the login form:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
tabBarController.delegate = self;
// Add the tab bar controller's view to the window and display.
self.window.rootViewController = self.tabBarController;
[self addTabBarArrow];
LoginViewController *loginViewController = [[LoginViewController alloc] init];;
[window addSubview:tabBarController.view];
[self.tabBarController presentModalViewController:loginViewController animated:YES];
[window makeKeyAndVisible];
return YES; }
In the log in modal view LoginViewController.h (child) I've got a protocol implemented:
#protocol PassUserInfoDelegate <NSObject>
#required
- (void) passUserInfo: (NSString *)string;
#end
When the user fills out the form, I create a NSURLConnection, and in the connectionDidFinishLoading method I get the user values from an JSON request:
-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSString *respuestaServidor = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
self.responseData = nil;
NSDictionary *dictionary = [respuestaServidor JSONValue];
idJson = [dictionary objectForKey:#"id"];
NSString *user_loginJson = [dictionary objectForKey:#"user_login"];
if ([idJson isEqualToString:#"null"] && [user_loginJson isEqualToString:#"null"]) {
NSLog(#"Login incorrecto");
} else {
NSLog(#"Procedo a loguear usuario");
}
[indicator stopAnimating];
[indicator release];
}
In HomeViewController.h (parent) I've got the delegation:
#interface HomeViewController : UIViewController <PassUserInfoDelegate> {
LoginViewController *protocolTest;
IBOutlet UILabel *nombreUsuario;
NSString *usuario;
}
#property (nonatomic, retain) IBOutlet UILabel *nombreUsuario;
#property (copy) NSString *usuario;
- (void) passUserInfo:(NSString *)string;
#end
and in the HomeViewController.m I implement the Protocol method:
- (void) passUserInfo:(NSString *)jSonString
{
userName.text = [[NSString alloc] initWithFormat:#"Welcome %#", jSonString];
}
and in the viewDidAppear method I call the loginSuccess method implemented in the LoginViewController class
-(void) viewDidAppear:(BOOL)animated{
protocolTest = [[LoginViewController alloc] init];
[protocolTest setDelegate:self];
[protocolTest loginSuccess];
}
loginSuccess method implemented in the LoginViewController class:
- (void)loginSuccess
{
[[self delegate] passUserInfo:idJson];
}
and it should pass the idJson value to the HomeViewController (parent).
The problem is that when I dismiss the modal view form, the idJson value is "NIL", so then in the HomeViewController I can't get this value. If I make this instead:
[[self delegate] passUserInfo:#"hello"];
I get the hello string in the HomeViewController (parent)
What am I doing wrong??
Thanks in advance!!!
Your problem is that instead of using the existing LoginViewController that has the actual data. Your viewWillAppear is creating a new one that never made the connection and getting its empty data.
First in the app delegate, you need to set your HomeViewController (the one in the tabbar) as the delegate to the LoginViewController that you're presenting.
Then, from connectionDidFinishLoading: you should be calling the [delegate passUserInfo:idJson]; to inform the HomeVC that the login screen got data.
You HomeVC's passUserInfo: method should probably dismiss the LoginVC with [self.tabBarController dismissModalViewControllerAnimated:YES]; (Since the login view was presented from the tabbar controller).

After setting UITextView's text property, screen does not update accordingly

I have view controllers A(FileListViewController) and B(TextFileViewController). A is a UITableViewController. What I am doing now is that after selecting a row in controller A, I load a text file and display that text in a UITextView in controller B.
The following is the header and implementation part(some code is abridged) of my the two controllers.
FileListViewcontroller Interface:
#interface FileListViewController : UITableViewController {
NSMutableArray * fileList;
DBRestClient* restClient;
TextFileViewController *tfvc;
}
#property (nonatomic, retain) NSMutableArray * fileList;
#property (nonatomic, retain) TextFileViewController *tfvc;
#end
FileListViewController Implementation:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
DBMetadata *metaData = [fileList objectAtIndex:indexPath.row];
if(!metaData.isDirectory){
if([Utils isTextFile:metaData.path]){
if(!tfvc){
tfvc = [[TextFileViewController alloc] init];
}
[self restClient].delegate = self;
[[self restClient] loadFile:metaData.path intoPath:filePath];
[self.navigationController pushViewController:tfvc animated:YES];
}
}
- (void)restClient:(DBRestClient*)client loadedFile:(NSString*)destPath {
NSError *err = nil;
NSString *fileContent = [NSString stringWithContentsOfFile:destPath encoding:NSUTF8StringEncoding error:&err];
if(fileContent) {
[tfvc updateText:fileContent];
} else {
NSLog(#"Error reading %#: %#", destPath, err);
}
}
And here is the interface for TextFileViewController:
#interface TextFileViewController : UIViewController {
UITextView * textFileView;
}
#property (nonatomic, retain) IBOutlet UITextView * textFileView;
-(void) updateText:(NSString *) newString;
#end
TextFileViewController implementation:
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:#selector(done)] autorelease];
textFileView = [[UITextView alloc] init];
}
- (void) updateText:(NSString *)newString {
NSLog(#"new string has value? %#", newString);
[textFileView setText:[NSString stringWithString:newString]];
NSLog(#"print upddated text of textview: %#", textFileView.text);
[[self textFileView] setNeedsDisplay];
}
(void)restClient: loadedFile: will be call after the loadFile:intoPath: is completed in the disSelectRowAtIndexPath method.
In TextFileViewController's updateText method, from NSLog I see that the text property is updated correctly. But the screen does not update accordingly. I've tried setNeedsDisplay but in vain. Did I miss something?
Thanks in advance.
In -[TextFileViewController viewDidLoad] you're creating a UITextView, but its frame is never set, and it's not added to the view hierarchy.
Try changing this:
textFileView = [[UITextView alloc] init];
to this:
textFileView = [[UITextView alloc] initWithFrame:[[self view] bounds]];
[[self view] addSubview:textFileView];
The problem is that textFileView is created in the viewDidLoad method of TextFileViewController. This method has not yet been called by the time you call updateText (this happens before the TextFileViewController is pushed).
You can fix this by forcing the view to load before you call [[self restClient] loadFile:metaData.path intoPath:filePath];.

What am I doing wrong with my code, when trying to create an email in iOS? (Help me understand protocols/delegates)

I'm trying to take my "myEmail" class and have it be my "all email methods go here" class,
and any other class that will email, will use "myEmail". "myEmail" must include MessageUI framework, and
then instiantiate an email controller to animate on the screen.
The complication is that I don't understand how to use "myEmail", which uses "MFMailComposeViewController",
in my "Documents" view correctly. When I call "sendEmail" in my "Documents" class, it never shows the
email window slide into view.
I understand that I can cut out myEmail as the middle man and use the MessageUI framework methods right
in Documents view, but I don't want to go about it in that way.
If anyone could point out how I'm using protocols/delegates wrong, I'd really appreciate it.
Code in Question
This is my myEmail class
In myEmail.h:
#import <Foundation/Foundation.h>
#import <MessageUI/MessageUI.h>
#protocol myEmailDelegate <MFMailComposeViewControllerDelegate>
#required
-(void)sendEmail;
- (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error;
#end
#interface myEmail : MFMailComposeViewController {
id <myEmailDelegate> delegate;
}
#property(nonatomic,assign) id<MFMailComposeViewControllerDelegate> myEmailDelegate;
#end
In myEmail.m:
#import "myEmail.h"
#import "ConstructionDocuments.h"
#implementation myEmail
#synthesize myEmailDelegate;
-(void)sendEmail
{
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"mypdfdoc" ofType:#"pdf"];
NSData *myData = [NSData dataWithContentsOfFile:filePath];
MFMailComposeViewController *controller = [[MFMailComposeViewController alloc] init];
controller.mailComposeDelegate = myEmailDelegate;
[controller setSubject:#"Email Example"];
[controller setMessageBody:#"Attached is pdf." isHTML:NO];
[controller addAttachmentData:myData mimeType:#"application/pdf" fileName:filePath];
[self presentModalViewController:controller animated:YES];
[controller release];
}
- (void)addAttachmentData:(NSData*)attachment mimeType:(NSString*)mimeType fileName:(NSString*)filename
{
}
- (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error {
[self becomeFirstResponder];
[self dismissModalViewControllerAnimated:YES];
}
- (void) dealloc
{
[myEmailDelegate release];
[super dealloc];
}
#end
This is the View where I'll be using my "myEmail" class
In Documents.h:
#import <UIKit/UIKit.h>
#import "myEmail.h"
#interface Documents : UIViewController <myEmailDelegate> {
}
#property(nonatomic, assign) id<MFMailComposeViewControllerDelegate> myEmailDelegate;
- (IBAction)sendEmail;
#end
#protocol myEmailDelegate <myEmailDelegate>
- (void) sendEmail;
#end
In Documents.m:
- (IBAction)sendEmail
{
myEmail *mymyEmail = [[myEmail alloc] init];
[mymyEmail setmyEmailDelegate: myEmailDelegate];
[myEmailDelegate sendEmail];
}
IIRC you need to call presentModalViewController: on an "active" view controller which is not the case for you. You could do something like this:
-(void)sendEmail:(UIViewController *)externalController
{
// ...
[externalController presentModalViewController:controller animated:YES];
// ...
}
And then call it from your action (which seems to be inside a view controller):
- (IBAction)sendEmail
{
myEmail *mymyEmail = [[myEmail alloc] init];
[mymyEmail setmyEmailDelegate:myEmailDelegate];
[mymyEmail sendEmail:self];
}
BTW, class names start with uppercase letter by convention.