objective c subclass UIImageView - objective-c

I'm trying to subclass UIImageView to add some custom functionality in it. I started trying to do the basics, just init an object with a given image and display it on the screen.
Using UIImageView everything works fine, but if i change my object from UIImageView to my custom class, no image is displayed.
Here's what i've done so far:
//Imager.h
#import <UIKit/UIKit.h>
#interface Imager : UIImageView {
}
-(id) init;
-(void) retrieveImageFromUrl: (NSString *)the_URL;
#end
//Imager.m
#import "Imager.h"
#implementation Imager
#synthesize image;
-(id)init
{
self = [super init];
return self;
}
-(void) retrieveImageFromUrl: (NSString *)the_URL{
//Not implemented yet
}
#end
So, i am using this statment: cell.postImage.image = [UIImage imageNamed:#"img.png"];
Before this is executed, i also have postImage = [[UIImageView alloc] init];
If postImage is declared as UIImageView everything works as expected. But if i change it to Imager instead, then nothing is displayed.
What am i missing?

You're synthesizing image, thus blocking calls to setImage on the superclass (i.e. UIImageView) when you attempt to use the dot notation to set the image.
Remove this line:
#synthesize image;

I would suggest using an Objective-C "Category" instead of subclassing the UIImageView. As long as you don't have to add any member variables, a Category is a better solution. When you use a category you can call your extended functions on any instance of the original class (in your case UIImageView. That removes the need for you to consciously use your subclass anywhere you might want to use your new functions.
You can just do the following in a header:
#interface UIImageView (UIImageView+URL)
-(void) retrieveImageFromUrl: (NSString *)the_URL;
#end
Then in an implimentation file:
#implimentation UIImageView (UIImageView+URL)
-(void) retrieveImageFromUrl: (NSString *)the_URL
{
// implimentation
}
#end
Then wherever you want to use your new function on a UIImageView, you just need to include the header file.

Related

Data encapsulation, Instance variable cannot be accessed

I'm having some trouble understanding what classes can read what variables in other classes. I've read to many different things online and cant seem to find anything solid in here. I've literally wasted the past two days trying to get my program to work but no classes can read any other classes variables. Any help will be GREATLY appreciated.
This is my ViewController.h:
#interface ViewController : UIViewController
{
#public
NSString *nameOfLabel;
}
#property (strong, nonatomic) IBOutlet UILabel *firstLabel;
- (IBAction)Switch:(id)sender;
- (IBAction)changeLabel:(UIButton *)sender;
-(NSString *) nameOfLabel;
#end
nameOfLabel is a public variable and should be able to be accessed by an outside class, right?
ViewController.m:
#import "ViewController.h"
#import "NewView.h"
#interface ViewController ()
#end
#implementation ViewController
- (IBAction)Switch:(id)sender {
NewView * new = [[NewView alloc] initWithNibName:nil bundle:nil];
[self presentViewController: new animated:YES completion:NULL];
}
- (IBAction)changeLabel:(UIButton *)sender {
nameOfLabel = #"Test Name";
_firstLabel.text = nameOfLabel;
}
-(NSString *) nameOfLabel {
return nameOfLabel;
}
#end
changeLabel button changes *firstLabel.text to "Test name".
second class is NewView.h:
#import "ViewController.h"
#interface NewView : UIViewController
#property (strong, nonatomic) IBOutlet UILabel *secondLabel;
- (IBAction)changeSecondLabel:(UIButton *)sender;
#end
and NewView.m:
#import "NewView.h"
#interface NewView ()
#end
#implementation NewView
{
ViewController *view;
}
- (IBAction)changeSecondLabel:(UIButton *)sender {
view = [[ViewController alloc] init];
_secondLabel.text = view.nameOfLabel;
}
#end
changeSecondLabel should change secondLabel.text to nameOfLabel which is 'Test name', however, the label actually disappears which makes me think that nameOfLabel cannot be reached. Ive played around with nameOfLabel, making it a #property and then synthesising it, as well as trying putting it in { NSString *nameOfLabel; } under #implementation but I still get the same result.
This line: view = [[ViewController alloc] init]; creates a new ViewController which doesn't know anything about what you may have done to some other ViewController. In your case, it specifically doesn't know that changeLabel: was called on another ViewController before this new one ever existed.
When the second view controller (NewView) is presented, it has no reference to the first view controller (ViewController) and it's data.
Here are a couple of suggestions.
In modern Objective-C I'd recommend using properties instead of exposing a variable.
Look over the naming in general. "ViewController" is not a good name for example.
If the property is part of an internal state of the class, declare it in a class extension.
Before you present the second view controller, set a reference to the string from the first view controller.
Part of ViewController.m:
#interface ViewController ()
#property (copy,nonatomic) NSString *nameOfLabel;
#end
#implementation ViewController
- (IBAction)Switch:(id)sender {
NewView *new = [[NewView alloc] initWithNibName:nil bundle:nil];
new.secondLabel.text = self.nameOfLabel;
[self presentViewController: new animated:YES completion:NULL];
}
First of all please read about coding standards, it's not a good practice to:
Name variables like "new"
Name methods like "Switch"
Name UIViewController like "view" or "NewView"
Regarding logic:
This is all messed up here. What you actually do is you create viewController with nameOfLabel which is empty and is only changed on button press. I assume you press that button so it's changed. Then on switch action you create another viewController and present it. Then from inside that new viewController you create another new viewController which has empty nameOfLabel, get this empty value and put it inside secondLabel.
There are couple of ways you can do to change secondLabel:
Move nameOfLabel to model and read it from there when you want to change secondLabel,
Because your new viewController is child of viewController that keeps nameOfLabel you can access it by calling [[self presentingViewController] nameOfLabel] but make it property first,
Pass nameOfLabel through designated initializer.
Well, if you want a simple demonstration of access of a public ivar, the syntax is:
view->nameOfLabel;
^^
not dot-syntax:
view.nameOfLabel;
(dot-syntax just goes through accessor methods).
I've only seen a handful of warranted edge cases over the years; there's rarely, rarely ever a good reason to make an ivar public (also, protected is also rarely a good choice).

Refer to a main view controller property by another class

I work on a project for iPad with Xcode 4.
I have a main view controller with many UITextField.
The TextFieldDelegate is a separate class in a separate file.
How can I refer, from TextFieldDelegate to a property (to a UITextField) of the main view controller (for example assign a value to a double)?
Thank you.
In most cases, if you want to use a separate delegate you should not need more information than what is passed to the delegate (the method's parameters). However, if you don't want to use your MainViewController as a delegate for your UITextField, you can initialize your TextFieldDelegate in your MainViewController instance and pass it the MainViewController instance.
For example you could have:
#import "MainViewController.h"
#interface TextFieldDelegate<UITextFieldDelegate> {
MainViewController* mainViewController;
}
#property(nonatomic,retain) MainViewController* mainViewController;
-(id)initWithController:(MainViewController*)controller;
#end
#implementation TextFieldDelegate
#synthesize mainViewController;
-(id)initWithController:(MainViewController*)controller {
if(self = [super init]) {
//some stuff
self.mainViewController = controller;
}
return self;
}
#end
Then in your MainViewController:
TextFieldDelegate tfd = [[TextFieldDelegate alloc] initWithController:self];
You just need to set the TextFields' delegate to tfd and you should be able to reference the MainViewController properties from the TextFieldDelegate instance. It's also possible to initiate it somewhere else, as long as you send the MainViewController instance to your TextFieldDelegate instance.
Edit: woups forgot a few '*'

Getting two errors in the code with the UIWebViewDelegate

I have a problem with this code:
Header:
#interface ViewController : UIViewController {
IBOutlet UITextField *field;
IBOutlet UIWebView <UIWebViewDelegate> *web;
}
Implementation:
#protocol UIWebViewDelegate;
- (void)viewDidLoad{
//some other code here
webView = [[UIWebView alloc] init];
webView.delegate = self;
}
I got two problems. The first is that on the line webView = [[UIWebView alloc] init];</code>the compiler generates the following message:Incompatible pointer types assigning to 'UIWebView*_strong' from 'UIWebView'*'.
The second problem is that I get an error on the line <code>webView.delegate = self;</code> that says:
'Passing ViewController*const_strong to parameter of incompatible type 'id'`.
Any ideas? Any help will be welcome.
You've declared your webView ivar to be a UIWebViewDelegate. You meant to make ViewController a UIWebViewDelegate:
#interface ViewController : UIViewController <UIWebViewDelegate>
Are you using a NIB? Because you are using an IBOutlet in your header file, which would suggest you have a NIB hooked up to it. In which case your web view will already be created, and you don't need the alloc/init call.
Also, why are you doing this:
#protocol UIWebViewDelegate;
...#protocol is used when you want to declare a protocol, not when you want to inidicate a class conforms to it. Perhaps take a quick look over Apple's documentation here:
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocProtocols.html

Setting the initial value of a UILABEL

I'm trying to create a simple Quiz app (I'm a beginner), when I launch the app I want a UILabel to show the first question (of an array of questions). I'm having some trouble with setting the initial value.
I've done a couple of attempts, whiteout success. I my QuizAppDelegate.h file I declare my UILabel like this:
IBOutlet UILabel * questionField;
In my main .m file I have tried the following:
- (id)init {
[super init];
questions = [[NSMutableArray alloc] init];
// Not working
questionField = [[UILabel alloc] init];
[questionField setText:#"Hello"];
// Working
NSLog(#"Hello");
[self defaultQuestions];
// [self showQuestion];
return self;
}
Another thing I have tried is the following in QuizAppDelegate:
#property (nonatomic, retain) IBOutlet UILabel *questionField;
- (void)changeTitle:(NSString *)toName;
And in the .m file:
#synthesize questionField;
- (id)init {
[super init];
questions = [[NSMutableArray alloc] init];
// Not working
[self changeTitle:#"Hello"];
// Working
NSLog(#"Hello");
[self defaultQuestions];
// [self showQuestion];
return self;
}
-(void)changeTitle:(NSString *)toName {
[questionField setText:toName];
}
Any tips on how to solve this would be great!
// Anders
Hopefully you're not actually putting code into main.m. On iOS, you rarely modify that file.
Since you're doing everything in the AppDelegate, let's keep it there (as opposed to creating a new UIViewController). Let's start with the basics.
Adding the Label as an instance variable
You're doing this correctly—inside the curly braces of the .h file, put the line
IBOutlet UILabel * questionField;
Then, declare the corresponding property, and make sure to synthesize it in the .m file.
#property (nonatomic, retain) IBOutlet UILabel *questionField;
#synthesize questionField // in the .m file
Adding the UILabel in Interface Builder
Open up MainWindow.xib. Drag a UILabel from the Library to the Window that represents your app's window. Then Control-Drag from the AppDelegate object (the third icon on the left in Xcode 4; it'll be labelled in the Document window in IB 3). You'll see a little black window come up—select the option called questionField to make the connection.
See this link for screenshots and how to make connections in IB. The same applies in Xcode 4.
Changing the text
You don't need a separate method to change the text—just modify the label's text property.
Pick a method that'll be called when the app launches (applicationDidFinishLaunching:WithOptions: is a good place to do it in), and put the following code:
questionField.text = #"Hello";
And that's it!
Code
QuizAppDelegate.h
#import <UIKit/UIKit.h>
#interface QuizAppDelegate : NSObject <UIApplicationDelegate> {
IBOutlet UILabel *questionField;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) IBOutlet UILabel *questionField;
#end
QuizAppDelegate.m
#import "QuizAppDelegate.h"
#implementation QuizAppDelegate
#synthesize window=_window;
#synthesize questionField;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
// Add the tab bar controller's current view as a subview of the window
[self.window addSubview:self.questionField];
[self.window makeKeyAndVisible];
self.questionField.text = #"Hello";
return YES;
}
- (void)dealloc
{
[_window release];
[questionField release];
[super dealloc];
}
#end
If you're creating the label programmatically, then you have to add the label to the view:
[self.view addSubview:questionField];
This assumes that you have a ViewController. If not, and you're doing this directly in the AppDelegate (a very bad idea, by the way), then do
[self.window addSubview:questionField];
If you're creating it in the IB, make sure you set up the connections.
You should not both add the UILabel in the IB and instantiate it programmatically. Only call alloc if you are creating it programmatically. Otherwise, if using the IB, skip that part. You created it already with the xib.
I suspect that you have either not created your Interface Builder layout properly - either you have missed the control out all together or more likely you have not connected that control to the questionField outlet in yout header file.
You need to drag a UILabel view into the main view and then connect it to the correct line in your header file.
You shouldn't be using your main.m like that at all. In fact, you should almost certainly never do anything with it. Try creating a UIViewController subclass and practicing your quiz with that. (Add the UILabel to the IB file and then connect the outlet.) Perhaps use the View-Based Application template while you are practicing.
This is a good answer:
"You're doing this correctly—inside the curly braces of the .h file, put the line
IBOutlet UILabel * questionField;"
I was trying to change the value of mylabel.text and the screen didn't update the label with this.value. I included the {IBOutlet UILabel * mylabel} and it works like a charm!
So this answer is valid to change the text of a label programmatically!
Thanks

How to traverse the class hierarchy through a variable assignment in Cocoa Touch?

I have created a UIViewController class called MyViewController with a UIImageView in its XIB file. I then import this class into another class. I make an instance of the class and I change the image in the UIImageView using code:
myViewController.myImageView.image = [UIImage imageNamed:#"myImage.png"];
This works swimmingly. My app is essentially an image viewer. I wanted to cache next and previous images by preloading a subview with an image. When I place myViewController into a variable like this:
UIViewController *pager = myViewController;
And attempt to use the variable to set the image for the UIImageView like this:
pager.myImageView.image = [UIImage imageNamed:#"myImage.png"];
I get an "error: request for member 'myImageView' in something not a structure or union". I've tried doing it using square brackets:
[[[pager myImageView] setImage:[UIImage imageNamed:#"myImage.png"]];
I get "warning 'UIViewController' may not respond to '-myImageView' ". How do I access the hierarchy to get to myImageView? I've used a #class in the header and a #import and I've synthesized the class instance. The only solution I have so far is a hack, by doing this:
UIImageView *pager = myViewController.myImageView;
pager.image = [UIImage imageNamed:#"myImage.png"];
Which doubles the amount of variables I have, one for the UIImageView variable, and one for the UIViewController variable. Thanks in advance to anyone that can help out.
quick answer:
You'll need to change the line:
UIViewController *pager = myViewController;
to:
MyViewController *pager = myViewController;
To be able to access the custom properties you added in your MyViewController class.
explanation:
Your MyViewController class probably looks something like this (simplified):
// MyViewController.h:
#interface MyViewController : UIViewController
{
UIImageView * myImageView;
}
#property (readonly) UIImageView * myImageView;
#end
// MyViewController.m:
#implementation MyViewController
#synthesize myImageView;
#end
You have extended the UIViewController class by adding a member variable and a property (myImageView) to the original class.
When you use a pointer to the base class (UIViewController) to access your variable, you are telling the compiler to treat that variable as if it was a plain old UIViewController, and a plain old UIViewController has no concept of a myImageView. This is what causes the compiler errors.