I'm new to Objective C and I'm having trouble getting delegates to work in my code.
I have a class called cViewController and in this class has a UIWebView to fetch webpages and a button above it that calls a popover class called prodMenu. What I'm having trouble with is that the function to call webpages from the prodMenu class and display it on the parent UIWebView isn't working.
cViewController.h
#import <UIKit/UIKit.h>
#import "prodMenu.h"
#interface cViewController : UIViewController <prodMenuDelegate>{
UIPopoverController *cpopover;
IBOutlet UIWebView *webImageDisplay;
}
#property (nonatomic, retain) UIWebView *webImageDisplay;
#property (nonatomic, retain) UIPopoverController *cpopover;
- (IBAction)cPopoverTapped:(id)sender;
- (void) fetchWebsite:(NSString *)website; //This is the delegate method I'm trying to call in the popover class.
#end
cViewController.m
- (IBAction)cPopoverTapped:(id)sender {
prodMenu *tp = [[prodMenu alloc] init];
cpopover = [[UIPopoverController alloc] initWithContentViewController:tp];
[tp release];
CGRect rect = CGRectMake(40, 0, 50, 50);
cpopover.popoverContentSize = CGSizeMake(250, 225);
[cpopover presentPopoverFromRect:rect inView:self.view permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES];
}
- (void)fetchWebsite:(NSString*)website{ //This function is called by prodMenu class
NSLog(#"address: %#", website);
NSString *urlAddress = website;
NSURL *url = [NSURL URLWithString:urlAddress];
NSURLRequest *requestObj = [NSURLRequest requestWithURL:url];
[webImageDisplay loadRequest:requestObj];
[webImageDisplay release];
}
- (void)viewDidLoad {
[self fetchWebsite:#"http://www.yahoo.com"];
[super viewDidLoad];
}
prodMenu.h
#import <UIKit/UIKit.h>
#protocol prodMenuDelegate;
#interface prodMenu : UIViewController{
<prodMenuDelegate> delegate;
}
- (IBAction)radBtn:(id)sender; //This is the IBAction that calls the fetchWebsite function
#property (nonatomic, assign) id <prodMenuDelegate> delegate;
#end
#protocol prodMenuDelegate
- (void)fetchWebsite:(NSString *)website;
#end
prodMenu.m
#import "prodMenu.h"
#import "cViewController.h"
#implementation prodMenu
#synthesize delegate;
- (IBAction)radBtn:(id)sender{
[delegate fetchWebsite:#"http://www.google.com"]; //Here is the call to the method
}
Am I missing something here to call the delegate method? There aren't any errors or warnings. All I want to do is when the user presses a button on the popover view, the parent class underneath changes the webpage. Thanks in advance.
Just a quick idea. Have you tried declaring the delegate protocol like this
#protocol prodMenuDelegate <NSObject>
...
#end
? I often ran into troubles when omitting the < NSObject >. Also I think there is an 'id' missing in
#interface prodMenu : UIViewController{
<prodMenuDelegate> delegate;
}
Edit: Also, in the code you posted I do not see where you actually assign the prodMenu's delegate.
Related
Here is the situation. I have a view controller titled "MyViewController." Within this view controller I have a text editing feature that uses subclassed buttons. The name of the UIButton Subclass is "ColorSwatch"
I have setup delegate/protocol methods in the "ColorSwatch.h" subclass as follow.
// ColorSwatch.h
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <QuartzCore/QuartzCore.h>
#protocol ColorSwatchDelegate <NSObject>
- (void)fontColor:(UIColor *)color;
#end
#interface ColorSwatch : UIButton {
id <ColorSwatchDelegate> colorSwatchDelegate;
CAGradientLayer *gradient;
UIView *currentView;
UIColor *fontColor;
}
#property (nonatomic, retain) id <ColorSwatchDelegate> colorSwatchDelegate;
#property (nonatomic, retain) CAGradientLayer *gradient;
#property (nonatomic, retain) UIView *currentView;
#property (nonatomic, retain) UIColor *fontColor;
#end
Now in my "ColorSwatch.m" I have:
// ColorSwatch.m
#import "ColorSwatch.h"
#import <QuartzCore/QuartzCore.h>
#import "MyViewController.h"
#implementation ColorSwatch
#synthesize gradient;
#synthesize currentView;
#synthesize colorSwatchDelegate;
#synthesize fontColor;
-(void)setupView{
"Makes the subclassed buttons pretty"
}
-(id)initWithFrame:(CGRect)frame{
if((self = [super initWithFrame:frame])){
}
return self;
}
-(id)initWithCoder:(NSCoder *)aDecoder{
if((self = [super initWithCoder:aDecoder])){
[self setupView];
MyViewController *mvc = [[MyViewController alloc] initWithNibName:
#"MyViewController" bundle:nil];
self.colorSwatchDelegate = mvc;
}
return self;
}
-(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
[self magnify:view];
fontColor = view.backgroundColor;
[self.colorSwatchDelegate fontColor:fontColor];
}
- (void)magnify:(UIView *)view
{
}
- (void)dealloc
{
[currentView release];
[gradient release];
[fontColor release];
[super dealloc];
}
#end
In the "MyViewController.h" I have:
// MyViewController.h
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#import "ColorSwatch.h"
#interface MyViewController : UIViewController <ColorSwatchDelegate> {
UITextField *photoLabelTextField;
}
#property (nonatomic, retain) IBOutlet UITextField *photoLabelTextField;
#end
In the "MyViewController.m" I have:
- (void)fontColor:(UIColor *)color
{
NSLog(#"Selected Font Color");
[self.photoLabelTextField setTextColor:color];
}
Now the delegate method sort of works, meaning when I tap on a color button the
NSLog(#"Selected Font Color");
message gets fired. But the problem is that I cannot change the
[self.photoLabelTextField setTextColor:color];
property. I have tried numerous ways of changing the property, the only thing that I can do is send NSLogs, anything I try to change a property in the "MyViewController" Class nothing happens.
If anyone could please help me out, I would appreciate it.
Thank you
The problem is that the ColorSwatch is sending delegate messages to a dangling instance of MyViewController that it incorrectly allocated in it's initWithCoder: method.
UIControls shouldn't allocate ViewControllers to be their delegates... it goes the other way around.
Delete these lines...
// in ColorSwatch.m initWithCoder:
MyViewController *mvc = [[MyViewController alloc] initWithNibName:
#"MyViewController" bundle:nil];
self.colorSwatchDelegate = mvc;
Then, in MyViewController.m ...
- (void)viewDidLoad {
ColorSwatch *colorSwatchButton = [[ColorSwatch alloc] buttonWithType:UIButtonTypeCustom];
// or place a ColorSwatch in the xib, on MyViewController's view... But not before you
// you delete lines from initWithCoder, otherwise it's infinite circular allocation
colorSwatchButton.frame = CGRectMake(/* ... */);
colorSwatchButton addTarget:self action:#selector(csButtonPressed:) forControlEvent: UIControlEventTouchUpInside];
// and so on...
// now the important part:
colorSwatchButton.colorSwatchDelegate = self;
// see - the ViewController is in charge of allocation, sets itself up as the delegate
[self.view addSubview:colorSwatchButton];
}
Instead of building the button in code, you can use IB.
Step 1: make the delegate an outlet...
#property (nonatomic, retain) IBOutlet id <ColorSwatchDelegate> colorSwatchDelegate;
Step 2: draw the buttons in IB, and set their class to ColorSwatch.
Then you can skip the code I wrote in viewDidLoad.
Step 3: The newly placed button should now present an outlet in IB. You can drag from that to the MyViewController as you normally do.
There might be a connection problem in your IBOutlet photoLabelTextField, you may have forgotten to connect xib text field with your photoLabelTextField
I have an App Delegate and a 3 view controllers in my project. I have a variable(a NSMutable Array) in my App Delegate which I want to access from my view controllers. So I decided to create a pointer to my App Delegate and access the variables.
Here is my code:
iSolveMathAppDelegate.h
#import <UIKit/UIKit.h>
#interface iSolveMathAppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
UITabBarController *tabBarController;
NSMutableArray *tmpArray;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) NSMutableArray *tmpArray; // variable I want to access
#property (nonatomic, retain) IBOutlet UITabBarController *tabBarController;
#end
iSolveMathAppDelegate.m
#import "iSolveMathAppDelegate.h"
#implementation iSolveMathAppDelegate
#synthesize window;
#synthesize tabBarController;
#synthesize tmpArray;
...
- (void)dealloc {
[tabBarController release];
[window release];
[tmpArray release];
[super dealloc];
}
#end
The view controller class from which I want to access the tmpArray.
referenceViewController.h
#import <UIKit/UIKit.h>
#class iSolveMathAppDelegate;
#interface referenceViewController : UITableViewController {
NSMutableArray *equationTypes;
iSolveMathAppDelegate *data;
}
#property(nonatomic, retain) NSMutableArray *equationTypes;
#property(nonatomic, retain) iSolveMathAppDelegate *data;
#end
And finally referenceViewController.m
#import "referenceViewController.h"
#implementation referenceViewController
#synthesize equationTypes, data;
data = (iSolveMathAppDelegate *)[[UIApplication sharedApplication] delegate];
//says that initializer element is not constant...ERROR!
- (void)viewDidLoad {
[super viewDidLoad];
NSString *path = [[NSBundle mainBundle] pathForResource:#"equationTemplates"ofType:#"plist"];
data.tmpArray = [[NSMutableArray alloc] initWithContentsOfFile:path];
self.equationTypes = data.tmpArray;
[data.tmpArray release]; // obviously none of these work, as data is not set.
}
- (void)dealloc {
[super dealloc];
[equationTypes release];
[data release];
}
#end
So anyway at the line data = (iSolveMathAppDelegate *)[[UIApplication sharedApplication] delegate]; the compiler says that the initializer element is not constant.
I have scrouged the web for answers, and for all it seems to work...but no dice for me :( Can you please advice me on where I have gone wrong? I am using XCode 3.2 and iOS SDK 3....maybe the SDK is the problem.
Thank You
That line of code isn't in a method or function, so the compiler is treating it as the definition of a compile-time constant or static/global variable. Those need constant values for initialization.
You should put the assignment of data within a method. A good place would be -viewDidLoad:
- (void)viewDidLoad {
[super viewDidLoad];
data = (iSolveMathAppDelegate *)[[UIApplication sharedApplication] delegate];
...
}
I figured out the struct and union problem. All I had to do was change #class iSolveAppDelegate to #import "iSolveAppDelegate.h" in my referenceViewController.h file. Thanks Jonathan for your help!
I'm running a background operation using NSOperation, which gets kicked off from my AppDelegate. What I want to do is when the operation is complete, have it call a method in the RootViewController.m file.
Currently there's a property in my AppDelegate named navigationController, and I"ve been trying to set up the call like this:
AppDelegate.m:
GetRSSOperation *gro = [[GetRSSOperation alloc] initWithURL:rssURL target:self selector:#selector(dataSourceDidFinishLoadingNewData:)];
[queue addOperation:gro];
[gro release];
GetRSSOperation.m:
[target performSelectorOnMainThread:selector withObject:alerts waitUntilDone:YES];
I have tried setting the target to self, self.navigationController, self.navigationController.view but none work.
I am aware that the issue might be due to my not yet having a complete understanding of the entire ios architecture, so would appreciate any pointers please.
**Adding more code to explain:
AppDelegate.h
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#interface AppDelegate : NSObject {
NSOperationQueue *queue;
UIWindow *window;
UINavigationController *navigationController;
NSURL *rssURL;
// storage for the alert list from RSS feed
NSMutableArray *alerts;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) IBOutlet UINavigationController *navigationController;
#property (nonatomic, retain) NSMutableArray *alerts;
#property (nonatomic, retain) NSURL *rssURL;
#property (retain) NSOperationQueue *queue;
+ (id)shared;
#end
AppDelegate.m
#import "AppDelegate.h"
#import "RootViewController.h"
#import "AlertViewController.h"
#import "RefreshTableHeader.h"
#import "GetRSSOperation.h"
#implementation AppDelegate
static AppDelegate *shared;
#synthesize window, navigationController, alerts;
- (id)init
{
if (shared) {
[self autorelease];
return shared;
}
if (![super init]) return nil;
queue = [[NSOperationQueue alloc] init];
shared = self;
return self;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Add the navigation controller's view to the window and display.
UIImageView *imageview=[[UIImageView alloc]initWithFrame:CGRectMake(270, 3, 37, 37)];
imageview.image=[UIImage imageNamed:#"iphone.png"];
[self.navigationController.navigationBar addSubview:imageview];
[self.window addSubview:navigationController.view];
[self.window makeKeyAndVisible];
if (rssURL == nil) {
NSString *alertAddress = #"http://www.compoundstockearnings.com/com/public/selections.cfc?method=getrss&email=";
alertAddress = [alertAddress stringByAppendingString:cseLogin];
alertAddress = [alertAddress stringByAppendingString:#"&password="];
alertAddress = [alertAddress stringByAppendingString:csePass];
rssURL = [[NSURL alloc] initWithString:alertAddress];
}
GetRSSOperation *gro = [[GetRSSOperation alloc] initWithURL:rssURL target:self selector:#selector(dataSourceDidFinishLoadingNewData:)];
[queue addOperation:gro];
[gro release];
return YES;
}
return NO;
}
#end
GetRSSOperation just does the rss call, puts it in memory then tries to call the main navigation controller:
[target performSelectorOnMainThread:selector withObject:alerts waitUntilDone:YES];
The main navigation controller upon which I wish the thing to execute a selector, looks like this:
RootViewController.h
#import <UIKit/UIKit.h>
#interface RootViewController : UITableViewController {
IBOutlet RefreshTableHeader *alertTable;
UIImageView *imageView;
}
#end
RootViewController.m:
#import "RootViewController.h"
#import "AppDelegate.h"
#implementation RootViewController
- (void)dataSourceDidFinishLoadingNewData:(NSMutableArray *)tempAlerts
{
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
appDelegate.alerts = tempAlerts;
reloading = NO;
[alertTable flipImageAnimated:NO];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:.3];
[self.tableView setContentInset:UIEdgeInsetsMake(0.0f, 0.0f, 0.0f, 0.0f)];
[alertTable setStatus:kPullToReloadStatus];
[alertTable toggleActivityView:NO];
[UIView commitAnimations];
[popSound play];
}
Obviously I've shortened it a bit but that's the core of it. I just want the GetRSSOperation to be able to call RootViewController.m - dataSourceDidFinishLoadingNewData. any input appreciated.
It sounds like you're having a bit of trouble figuring out just how to format the call to performSelectorOnMainThread:withObject:waitUntilDone: correctly. It's pretty simple, really.
If you would normally call the method like this:
[target method:alerts]
you format the performSelector call like this:
[target performSelectorOnMainThread:#selector(method:) withObject:alerts waitUntilDone:YES];
Scenario: Need to execute CustomWebview delegate on view controller.
Please help me with this code. Instead of using callback, I need to use "Protocol". Can it be done or we can only use callback in this scenario.
On ViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
//MyWebView *webView = [[MyWebView alloc] initWithDelegate:self callback:#selector(finishLoading)];
MyWebView *webView= [[MyWebView alloc] initWithFrame:CGRectMake(0,0,320, 460)];
[webView LoadURL:#"http://192.168.5.165/"];
[webView setDelegate:self];
[webView setCallback:#selector(finishLoading)];
[self.view addSubview:webView] ;
}
- (void) finishLoading
{
NSLog(#"Finish");
}
On MyWebView.h
#interface MyWebView : UIView<UIWebViewDelegate> {
NSString *strURL;
}
#property(nonatomic, retain) NSString *strURL;
#property (nonatomic, assign) id delegate;
#property (nonatomic, assign) SEL callback;
-(void) LoadURL:(NSString*)url;
#end
On MyWebView.m
#import "MyWebView.h"
#implementation MyWebView
#synthesize strURL,delegate,callback;
UIWebView *webView;
-(id) initWithFrame:(CGRect)frame
{
if(self =[super initWithFrame:frame])
{
webView = [[UIWebView alloc] initWithFrame:frame];
webView.delegate = self;
[self addSubview:webView];
}
return self;
}
-(void) LoadURL:(NSString*)url
{
NSURL *u = [NSURL URLWithString:url];
NSURLRequest *req= [NSURLRequest requestWithURL:u];
[webView loadRequest:req];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
[delegate performSelector:callback];
}
Your question is a bit unclear. A protocol and a delegate are two entirely separate, though related, things--apples and oranges. A protocol defines a list of methods an object may or must respond to:
#protocol Document
+ (id)documentWithContentsOfURL: (NSURL *)aURL;
- (void)writeToURL: (NSURL *)aURL error: (NSError **)outError;
#end
A delegate is an object, usually an instance of a custom class, that is handed to another object for custom processing or feedback--that latter object delegates work to the delegate object.
Are you asking how to convert a delegate category on NSObject to a delegate protocol? (The former used to be Apple's pattern for defining the obligations and abilities of a delegate; the latter is the newer way to do the same thing.) If so, it generally looks something like this:
Delegate Category on NSObject
#interface NSObject (WidgetDelegate)
- (void)doSomethingWithWidget: (Widget *)w;
#end
#interface Widget : NSObject
#property (readwrite, assign) id delegate;
#end
Delegate Protocol
#protocol WidgetDelegate <NSObject>
- (void)doSomethingWithWidget: (Widget *)w;
#end
#interface Widget : NSObject
#property (readwrite, assign) id <WidgetDelegate> delegate;
#end
Is that what you're looking for? If not, can you clarify exactly what you're trying to do?
Also see Apple's Communicating with Objects, which discusses delegates, protocols, and selectors. Though its listed under Mac OS X, most (if not all) appears to apply to iOS also.
On ViewController.m
#interface MyViewController : UIViewController<MyFinishLoadingDelegate> {
//your own definition
}
- (void)viewDidLoad
{
[super viewDidLoad];
//MyWebView *webView = [[MyWebView alloc] initWithDelegate:self callback:#selector(finishLoading)];
MyWebView *webView= [[MyWebView alloc] initWithFrame:CGRectMake(0,0,320, 460)];
[webView LoadURL:#"http://192.168.5.165/"];
[webView setDelegate:self];
[self.view addSubview:webView] ;
}
- (void)finishLoading //this is your implementation for MyFinishLoadingDelegate
{
NSLog(#"Finish");
}
On MyWebView.h
//you declare the protocol here to tell anyone who'd like to act like your delegate that they need to implement "finishLoading"
#protocol MyFinishLoadingDelegate <NSObject>
- (void)finishLoading;
#end
#interface MyWebView : UIView<UIWebViewDelegate> {
NSString *strURL;
}
#property(nonatomic, retain) NSString *strURL;
#property (nonatomic, assign) id<MyFinishLoadingDelegate> delegate;
-(void) LoadURL:(NSString*)url;
#end
On MyWebView.m
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
[delegate finishLoading];
}
I have an UIViewController named MainViewController
I have another UIViewController named LeftSharingViewController;
I would like to get and use the NSString from MainViewController in my LeftSharingViewController
I have a problem, I always get (null) instead of the NSString wanted value.
Here's my code and how does the NSString get it's value
MainViewController:
- (void)webViewDidFinishLoad:(UIWebView *)webView {
leftWebViewString = [NSString stringWithString:leftWebView.request.URL.absoluteString];
}
LeftSharingViewController.h:
#import <UIKit/UIKit.h>
#import "MainViewController.h"
#import <MessageUI/MessageUI.h>
#import <MessageUI/MFMailComposeViewController.h>
#class MainViewController;
#interface LeftSharingViewController : UIViewController <MFMailComposeViewControllerDelegate> {
MainViewController *mainViewController;
NSString *leftWebViewUrl;
}
#property (nonatomic, retain) MainViewController *mainViewController;
#property (nonatomic, retain) NSString *leftWebViewUrl;
#end
LeftSharingViewController.m:
#import "LeftSharingViewController.h"
#import "MainViewController.h"
#implementation LeftSharingViewController
#synthesize mainViewController;
#synthesize leftWebViewUrl;
- (void)viewWillAppear:(BOOL)animated {
self.leftWebViewUrl = self.mainViewController.leftWebViewString;
}
#pragma mark -
#pragma mark Compose Mail
-(void)displayComposerSheet
{
MFMailComposeViewController *mailPicker = [[MFMailComposeViewController alloc] init];
mailPicker.mailComposeDelegate = self;
[mailPicker setSubject:#"Check Out This Website!"];
[mailPicker setMessageBody:[NSString stringWithFormat:#"Take a look at this site:%#", leftWebViewUrl] isHTML:YES];
mailPicker.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentModalViewController:mailPicker animated:YES];
[mailPicker release];
}
Thanks!
On your MainViewController, you are never retaining the NSString you create. You are using stringWithString:, which returns an autoreleased object.
You should either retain it, or (easier) change your code to use your accessor, which already retains, like this:
- (void)webViewDidFinishLoad:(UIWebView *)webView {
self.leftWebViewString = [NSString stringWithString:leftWebView.request.URL.absoluteString];
}
As a side note, if you are navigating from MainViewController to LeftSharingViewController, instead of "pulling" the data in LeftSharingViewController from MainViewController, I would do it the other way around:
In MainViewController, before displaying the view of LeftSharingViewController, I'd set the required properties.