Subclass from UIView or UIImageView? Overthinking a simple image downloading task. - ios

This question is two fold:
1) Understanding which subclass method is better when creating an Asynchronous download ImageView class
2) And how to use this class in a UITableViewController so that the class view is automatically adopted in a UITableViewCell.
Task at hand
I just received a fun little task from someone but I'm overthinking the solution.
Question 1 - Which method is better for my purposes?
Keeping in mind that I will have to reuse the class to display some images perhaps in a UITableViewController, I thought about two possible ways of going about implementing such a class:
1) Either create a class that subclasses from UIView, then add the required imageURL property, and then also add a UIImageView property container to this subclass which the image will download into once the asychronous request has been downloaded once the user set the URL property when he is to "re-use" the class in a table view controller.
Or
2) Create a class subclassing from UIImageView and then only add that one required property imageURL which would then save me the mess of having to progammatically create an image container.
Question 2: Whats the best way to re-use this Asynchronous ImageView class that I have created in a UITableViewController.
The problem (at least what I believe may be a stumbling block) is when I create the table view controller, whats the best way to have this image set the image property of a UITableView cell?
something like
AsynchronousImageViewDownloader *myImageView = [[AsynchronousImageViewDownloader alloc] init];
[myImageView setImageURL:[NSURL urlWithString:#"url.com/image.jpg"]];
//At this point im not sure how to have the image display in the image property below.
//Remember that the image should automatically show in the view when the url has been set.
cell.image = ??
How would I go about doing something like this?

Create a subclass of UITableViewCell, and add a public property to that class for your AsynchronousImageViewDownloader imageView. In init, create the imageview (it will not have a URL at this time) and add it to self.contentView.
Then in cellForRow you'll be able to do something like
cell.customImageView.url = #"someURL"

Create UIImageView subclass with a single new property, imageURL. In the subclass's implementation, override setImageURL with something similar to the following:
- (void)setImageURL:(NSURL *)imageURL
{
if (!_imageURL) {
_imageURL = imageURL;
// Do asynchronous image download, setting the self.image property, on the main thread, in the completion block.
}
return _imageURL;
}

Related

Subview acessing superview methods

How can a subview access the methods from its superview? I have a button, and when pressed I would like the button to call a method from its superview, but I do not know how.
suppose your super View class name is
MainView.h
sub View Name
SubView.h
So In sub class you can do
MainView *myMainView = (Mainview *)[self superview];
[myMainView someMethod];
Make sure someMethod is public Method.
Other way you could have reference to all the view is set a tag
For example
myMainView.tag = 100; or self.tag = 100;
In the subview you could do
MainView *myMainView = (Mainview *)[self viewWithTag:100];
[myMainView someMethod];
a weird construct but just call the method:
inside a view you have have self.superview
since self.superview is a UIView*, the compiler will claim it is invalid to call method XYZ on it. Cast it to id or to your class name to use it
e.g.
[(id)self.superview myMethod];
or even
id myValue = [(id)self.superview myMethod:param1];
One method is to use delegates.
#protocol ButtonHandlingDelegate <NSObject>
- (void) subviewButtonWasPressed;
#end
In your Subview add this:
#property (nonatomic, weak) id selectionDelegate;
When subview is created, set delegate to superview.
Define Superview as delegate in .h file
#interface SuperView : UIView <ButtonHandlingDelegate>
in Superview .m file
- (void) subviewButtonWasPressed{
// Do somethign about it
}
All of the answers listed are hacky and bad style. You should be using delegation through the subview's ViewController. What you will want to do is create a protocol for your subview with a void method called something like specialButtonPressedOnView:(SUBVIEWCLASS *)view. Then in the subview's view controller you should make yourself the delegate for this protocol and respond to the delegate method from the VC's context.
Using self.superview is a bad idea because you cannot guarantee what your superview is going to be and generally blindly calling method on objects (ESPECIALLY those cast to id) is a really bad idea.
The other answer that suggested having the superview implement the subview's protocol is also not good style because you're creating a dependency between your views. If you were thinking of going down this path your subview should probably just be a subclass of the superview.
One of the core parts of the MVC design pattern is making sure that your views are reusable and totally independent. They are just structures that you can inject your content into, to which they will respond with pretty UI, and they don't need to know anything about what's being passed to them and don't have to ask other views for help. So either using delegation through the subview's ViewController or subclassing is probably the best direction. Both methods preserve the MVC pattern and are much safer than the other suggestions.

How can I get the ViewController instance starting from subView

I've an object of UIImageCustom that extend UIImageView, and in a method this class I want to get instance the ViewController, where my object UICustom is added.
In touchesBegan method of my class UIImageCustom, when I touch in UIImage, I want add other UIImage in my ViewController:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UIImageView* newImageView=[[UIImageView alloc] initWithFrame:CGRectMake(60, 200, 200, 200)];
//Here I need add newImageView in my ViewController
[??? addSubview:newImageView];
}
Someone can help me?
You'll have to add a custom property to your UIImageCustom class that contains a reference to the owner view controller if you want to do what you're saying you want to do. But there's a better way.
What I suggest you should be doing is implementing touchesBegan in the view controller that owns the view, and performing any custom logic there.
There are a bunch of ways you could do this (set a custom property in your UIImageCustom class, just have UIImageCustom add this new image view to its own superview (e.g. [self.superview addSubview:newImageView]). But these approaches seem conceptually flawed. A view shouldn't be adding anything other than to itself (and if you turned off clipping, adding this new image view as a subview of the UIImageCustom, itself, might be another approach, certainly less offensive than the previous two I alluded to).
But I might suggest a couple of other approaches:
If your goal is simply to avoid duplication of your touches code, I might put it in a gesture recognizer subclass, rather than attaching it to some random UIImageView subclass. Gesture recognizers is the right place to put this sort of touch-related code. See the Gesture Recognizers section of the Event Handling Guide for iOS.
Or if you really need to marry this gesture with with a bunch of image views, I might use a custom container view controller to mediate this interaction between gestures and image views. See Creating Custom Container View Controllers in the View Controller Programming Guide for more information.
But you could wrap the touches code and the handling of these two image views all within a view controller, and then use view controller containment to add that to an existing view controller.
We don't know enough about the particular design to suggest one over another, but those are a couple of ideas to pursue. Most likely, the custom gesture recognizer approach should be more than sufficient.
Create a category like this
#interface UIViewController (TOP_MODAL)
-(UIViewController*) topModalController;
#end
#implementation UIViewController (TOP_MODAL)
-(UIViewController*) topModalController{
if (self.presentedViewController == nil){
return self;
}
return self.presentedViewController.topModalController;
}
#end
then
AppDelegate* delegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
UIViewController* viewController = [delegate.window.rootViewController topModalController];
...
[viewController.view addSubview:newImageView];

Programmatically set an IB object's Class

When using Interface Builder, a lot of the time I'd set the Class property to one of my custom classes. Say I have a UIView in the nib and I set the class to my MyView class.
That's all well and good, even when loading the nib programmatically.
But, is there a way to set the Class of a UI control when programmatically loading a nib? The reason being that I want to sometimes use the same nib created in IB, but have it associated with a different Class.
I don't think there is a proper way to achieve that programmatically.
You could create a naked UIView object in IB and give it a tag or assign it to an IBOutlet for its identification so taht you can access it programmatically in viewDidLoad.
In viewDidLoad you access it and fetch its frame and superview and probably background colour, alpha, hidden status etc, so that you can set all those values in IB. Then remove the UIView from its superview and nil the property (if any) or release the object respectively (depending on ARC or not). Then create the UI Element that you need, assign it to the property (if any) add it to the superview of the former UIView, set its frame and properties accordingly and go from there.
(There are ways in Obj-C to change an object's class after its instanciation, but I would not recommend doing that in this case, especially when your subclasses come with additional properties and ivars.)
Normally you create a UIView object on your UIViewController's class XIB file using Interface Builder and then you use the Custom Class tool in the Identity Inspector to associate the UIView with a class (The class is a UIView class containing the code for drawing on the UIView object.). Then you have to use #property and #systhesize in your UIViewController class to hook the UIView (using the connections inspector) to your class. This method is OK but in certain circumstances it has limitations.
You can get around all of this pragmatically.
Create the UIView class that will be used to draw on a UIView object. In this class you create the context reference (CGContextRef) to give the drawing tools a context (where to draw) for such things as strings, lines, circles, etc. i.e.
CGContextRef gg = UIGraphicsGetCurrentContext();
In the UIViewController class, in the .h file you need to import a reference to your UIView class. Let's call it: DrawOnView
#import "DrawOnView.h"
Then in the brackets encompassing the #interface place this line:
UIView * draw; // draw can be changed to any name that suits your needs
Then in the .m part of the class inside the viewDidLoad method you need to insert this code:
// Make the size and location that suits your needs
// You can change this on the go in your code as needed, such as if the
// device orientation is changed.
draw = [DrawOnView alloc] initWithFrame:CGRectCreate(50, 50, 100, 200)];
// You can change the background color of the view, if you like:
[draw setBackGroundColor:[UIColor greenColor]];
// Now add the view to your primary view
[self.view addSubview:draw];
Now, in other parts of our program, you can call methods you have declared in the DrawOnView class and refresh (which calls the drawRect method, the primary entry point in your UIView class) by using this reference:
[(DrawOnView*) draw setNeedsDisplay];
This is very important. Do not use:
[draw setNeedsDisplay]; // This will not work!
Let's say you have other methods defined in DrawOnView and want to call them.
Here's an example method (in the .h file):
-(BOOL) wasHotSpotHit: (CGPoint) p;
The actual method could look like this (in the .m file):
-(BOOL) washHotSpotHit: (CGPont) p
{
if(CGRectContainsPont(hotspot.frame, p))
{
return true;
}
return false;
}
Use code like this:
if([(DrawOnView*) draw testIfSpotHit:p])
{
// Do something for when user touches hot spot.
}
Try using "object_setClass(id object, Class cls)" method of Objective c runtime. GoodLuck:)

What is the best way to determine if a line that was drawn in a view was tapped?

I have a view with several lines drawn in it in different directions. I need to determine which line was tapped by the user and then respond accordingly.
I have a few different ideas in my head, but I want the best, most efficient way to do this...
Ultimately what makes the most sense to me is having each line in a separate view and treated like individual objects. If I did this would I need to position and rotate the view to be exactly where the line is so I know when it is tapped? If not I would assume the views will overlap each other and I would not be able to determine what line was tapped.
I hope I am making sense. Please let me know the best way to achieve this. Thanks!
For me the best way to approach this is create UIView's as lines. If they are simply lines with plain colors just use the background view and set the CGRectFrame accordingly.
In order to react to touch event's without dealing with positions etc, create a touchEvent in the init method of the UIView like this:
UITapGestureRecognizer *onTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(lineClicked)];
[self addGestureRecognizer:onTap];
Declare the function inside the UIView class:
-(void)lineClicked {
//You can check some #property here to know what line was clicked for example
if (self.color == [UIColor blackColor])
//do something
else
//do another thing
// You can use a custom protocol to tell the ViewController that a click happened
(**) if ([self.delegate respondsToSelector:#selector(lineWasClicked:)]) {
[self.delegate lineWasClicked:self];
}
}
(**) You will probably want to put some logic into your viewController after clicking in the line. The best way to approach this is to declare a #protocol in your CustomUIView.h file and pass self as parameter so the viewController knows who was clicked:
#protocol LineClikedDelegate <NSObject>
#optional
- (void)lineWasClicked:(UIView *)line; //fired when clicking in the line
#end
Finally, create a #property in your CustomUIView to point the delegate:
#property id<DisclosureDelegate> delegate;
And in the ViewController. When you create lines as UIViews set the delegate like:
blackLine.delegate = self.
Implement the method - (void)lineWasClicked:(UIView *)line; in the ViewController and you are set.
As requested I'm adding this as an answer:
You could use layers instead of views for displaying the lines. This is both efficient in drawing and allows for hit testing to determine which line has been tapped.
Here's code for the geometry calculations.

How do I make a different left accessory image for each annotation appear?

I'm stuck here and I thought I'd ask if anyone can help me with this as I'm getting ready to tear my hair out.
I'm using the following tutorial/guide below and got as far the end and it works great for the one image but what I want is to be able to set a different image/thumbnail for each individual annotation
Tutorial/Guide
I've been trying to get this to work now for a while and I've just about had enough of looking at it. lol
I'm pretty new when it comes to xcode so that's why I'm struggling with it.
From OP's "suggested edit to the answer":
EDIT:
I Can't seem to get what you said to work and I'm stumped with it.
I've added a copy of my project at Dropbox below if it's possible for you to check it. It maybe abit of a mess at the moment as I've tried for ages and getting no where.
https://www.dropbox.com/sh/6ddhege240konzo/XrpuFYF5y5
I'm tempted just to figure out how to do the custom callout instead.
One thing I'm unsure about is delegates and how they really work.
You want to set the leftCalloutAccessoryView to a different image for each annotation.
The leftCalloutAccessoryView is a property of MKAnnotationView so you need to set it in the viewForAnnotation delegate method (which is where you create and return an MKAnnotationView).
The viewForAnnotation delegate method gets a reference to the annotation that you need to create a view for in the annotation parameter.
So based on some property of annotation, you set leftCalloutAccessoryView accordingly.
At the crudest level, you could set leftCalloutAccessoryView based on annotation.title.
For example: if title is "SFO" set image to "apple", if title is "ATL" set image to "peach", etc.
However, it's much better to create a separate property (in your annotation class that implements MKAnnotation) that clearly indicates what image to use for the annotation. This property could be the UIImage itself, the name of the image, a number, etc. -- whatever is best for your situation.
When creating the annotation and before calling addAnnotation, you set this property of the annotation.
Then in the viewForAnnotation delegate method, you set the leftCalloutAccessoryView based on the custom annotation property.
For example, assuming an NSString property named imageName was added to the annotation class:
MKAnnotationView *av = ... //or MKPinAnnotationView
//typical dequeue and alloc/init code here
if ([annotation isKindOfClass:[MyAnnotationClass class]])
{
//cast the annotation parameter to your custom class
//so you can easily access the custom properties...
MyAnnotationClass *myAnn = (MyAnnotationClass *)annotation;
//create UIImage based on custom property of annotation...
UIImage *img = [UIImage imageNamed:myAnn.imageName];
//create UIImageView to use for the leftCalloutAccessoryView...
UIImageView *iv = [[[UIImageView alloc] initWithImage:img] autorelease];
//if using ARC, remove the autorelease above
av.leftCalloutAccessoryView = iv;
}
return av;

Resources