I created a custom AQGridViewCell.
By using UIImageView everything works. The image appears and is clickable, but when I change the UIImageView to TTImageView I can't click on the image.
The same example as here below by just changing the imageview to UIImageView and the the setter message to an image, everything works as expected.
Here is my ImageGridCell.h
#import "AQGridViewCell.h"
#import <Three20/Three20.h>
#interface ImageGridCell : AQGridViewCell
{
TTImageView * _imageView;
NSString *urlPath;
}
#property (nonatomic, retain) NSString *urlPath;
#end
And here is my ImageGridCell.m
#import "ImageGridCell.h"
#implementation ImageGridCell
#synthesize urlPath;
- (id) initWithFrame: (CGRect) frame reuseIdentifier: (NSString *) aReuseIdentifier
{
self = [super initWithFrame: frame reuseIdentifier: aReuseIdentifier];
if ( self == nil )
return ( nil );
_imageView = [[TTImageView alloc] initWithFrame: CGRectMake(0, 0, 100, 100)];
[_imageView setContentMode:UIViewContentModeScaleAspectFit];
[self.contentView addSubview: _imageView];
return ( self );
}
- (void) dealloc
{
[_imageView release];
[super dealloc];
}
- (CALayer *) glowSelectionLayer
{
return ( _imageView.layer );
}
- (UIImage *) image
{
return ( _imageView.image );
}
-(void)setUrlPath:(NSString *)urlpath {
_imageView.urlPath = urlpath;
}
I faced the same situation, and here is the solution I came with.
My AQGridViewCell have both a TTImageView and a UIImageView.
I use the TTImageView to load the image, and when it's loaded, I pass it to the UIImageView which displays it.
To do that, my AQGridViewCell subclass implements the TTImageViewDelegate protocol. In my PostGridViewCell.h, I have :
#interface PostGridViewCell : AQGridViewCell <TTImageViewDelegate> {
#private
TTImageView *ttImageView_;
UIImageView *imageView_;
}
// #property here …
#end
In my implementation file, in the initWithFrame:reuseIdentifier: method, I init both my ttImageView and my imageView, but I only add the imageView as a subview of my cell. And I set the ttImageView delegate property to self (so I can be notified when the image is loaded) :
- (id)initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)aReuseIdentifier {
if ((self = [super initWithFrame:frame reuseIdentifier:aReuseIdentifier])) {
// init image
ttImageView_ = [[TTImageView alloc] initWithFrame:CGRectZero];
ttImageView_.delegate = self;
imageView_ = [[UIImageView alloc] initWithFrame:CGRectZero];
[self.contentView addSubview:imageView_];
// …
}
return self;
}
When the TTImaveView has loaded the image, it calls the imageView:didLoadImage: method on the delegate (which is the PostGridViewCell). In my implementation file, I have this method :
- (void)imageView:(TTImageView*)imageView didLoadImage:(UIImage*)image {
self.imageView.image = image;
[self setNeedsLayout];
}
Does it help?
Cheers,
Thomas.
Not quite sure what you mean by "clickable" - like a UIButton? What behavior do you want to accomplish when a user taps one of the images?
I can't see the immediate problem here, but I can give you information that may help.
Foremost, TTImageView and UIImageView both descend from UIView, TTImageView does not descend from UIImageView (important to note):
UIImageView : UIView
TTImageView : TTView : UIView
We don't know what is going on behind the scenes in UIImageView, so I can't see what is different there, but the documentation seems to make clear that the UIImageView's default behavior for userInteractionEnabled is NO - which is odd, because you are saying that it is working with UIImageView, not the other way around.
Have you tried setting the userInteractionEnabled property to YES on TTImageView?
The problem here is that TTImageView has userInteractionEnabled to YES. So the touch event is being held by the TTImageView, and AQGridView expects touches in cell Views. Simply add userInteractionEnabled=NO to the TTImageView in your cell and you are ready to go.
Related
I want to make an Advertising banner in my app. A bit like iAd's.
I was going to make it by having a UIImage on the view then assigning the banner image. I would then add a touch gesture so the user could click it and go to another view in my app. I Know That I can do this on one view quite easily but I want this to be on most views in the app. Whats the best way for adding the banner to more than one view with out writing the same code more that once?
The below design shows the sort of banner im after.
Thanks
#import <UIKit/UIKit.h>
#class custom;
#protocol adDelegate
- (void)viewAd:(NSString *)adRate;
#end
#interface custom : UIView
#property (strong, nonatomic) UIImage *viewImage;
#property (assign) id <adDelegate> delegate;
#end
// Main class
#import "custom.h"
#implementation custom
#synthesize viewImage;
#synthesize delegate;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
UIImageView *imageView = [[UIImageView alloc] initWithFrame:frame];
imageView.image = viewImage;
[self addSubview:imageView];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
if ((self = [super initWithCoder:aDecoder]))
{
}
return self;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.delegate viewAd:#"view"];
}
You can Create a UIView Class and call it BannerView for instance.
// in the bannerView.h
#import <UIKit/UIKit.h>
#interface BannerView : UIView{
UIImageView* bannerImage;
}
#property(nonatomic,retain) UIImageView* bannerImage;
#end
//in the bannerView.m
#import "BannerView.h"
#implementation BannerView
#synthesize bannerImage;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
bannerImage=[[UIImageView alloc]initWithImage:[UIImage imageNamed:#"banner-image.png"]];
bannerImage.frame=CGRectMake(0, 0, 320, 100);
[self addSubview:bannerImage];
// add a uibutton on top of the uiimageview and assign an action for it
// better than creating an action recogniser
UIButton* actionButton=[UIButton buttonWithType:UIButtonTypeCustom];
actionButton.frame=CGRectMake(0, 0, 320, 100);
[actionButton addTarget:self action:#selector(yourAction) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:actionButton];
}
-(void) yourAction{
// do what ever here like going to an other uiviewController as you mentionned
}
#end
Now you can call this view from any View Controller this way
BannerView* banner=[[BannerView alloc] initWithFrame:CGRectMake(0, 300, 320, 100)];
[self.view addSubview:banner];
Try creating a parent class from UIView where you do all the display and handling of the banner using your UIImageView and gesture recognizers. Then whichever views need this functionality, derive them from this parent class, and override default handling in method so that you can customize the behavior in your child class.
A few suggestions:
First, why not just use a UIButton instead of a UIImage with a Gesture? All you're really doing is replicating button functionality after all...
Second, I'd probably tackle the overall problem by creating a class that includes the UIButton, like so:
#interface YourSuperViewController : UIViewController
#property (nonatomic, strong) IBOutlet UIButton *adButton;
- (IBAction)adTouched:(id)sender;
#end
In the viewDidLoad for this class, create the button, and add it to the view, and add your ad-specific logic to the adTouched action.
Then create the rest of the views in your app as an instance of YourSuperViewController. Like so:
#interface SomeOtherViewController : YourSuperViewController
Now the SomeOtherViewController will auto-magically have the ad button and respond to a touch on it properly. Done!
What everyone else has said is the best way. If you need custom functionality, subclassing is probably the way to go.
I just wanted to add one pedantic thing. Its important to remember that a UIImage is not a view. There has never been a UIImage on the screen, ever. A UIImage is a model object. It is just a collection of data. A UIImageView is a view object and as such, a UIImageView can display itself on the screen.
This might seem overly pedantic and nitpicky, but its important to have these things sorted out in our heads in order to effectively use MVC (model, view, controller)
In order to create an absolute bottomed footer on top of a tableView I found that using UIToolbar for this and adding custom views for it worked fine.
My problem is that I already use this as a toolbar for a webview, and here with another background image than I need now.
By replacing the drawRect function in UIToolbar+addition.m I have a global toolbar for this that works fine in my webviews.
How can I expand this so that I can select which version(background) to use the different places?
My UIToolbar+addition.m:
#import "UINavigationBar+addition.h"
#implementation UIToolbar (Addition)
- (void) drawRect:(CGRect)rect {
UIImage *barImage = [UIImage imageNamed:#"toolbar-bg.png"];
[barImage drawInRect:rect];
}
#end
Try creating separate .h and .m files for each "version", and import the appropriate .h into the class file you'd like it to affect.
Why not just add a barImage property to your extension?
#interface UIToolbar (Addition)
#property (nonatomic, retain) UIImage *barImage;
#end
Then, in your implementation (I'm doing this assuming you're not using ARC. If you are, obviously remove the retain/release stuff):
#implementation UIToolbar (Addition)
#synthesize barImage = _barImage;
//Override barImage setter to force redraw if property set after already added to superView...
- (void)setBarImage:(UIImage *)barImage {
if (_barImage != barImage) {
UIImage *oldBarImage = [_barImage retain];
_barImage = [barImage retain];
[oldBarImage release];
//Let this UIToolbar instance know it needs to be redrawn in case you set/change the barImage property after already added to a superView...
[self setNeedsDisplay];
}
}
- (void) drawRect:(CGRect)rect {
[self.barImage drawInRect:rect];
}
//If you're not using ARC...
- (void)dealloc {
[barImage release];
[super dealloc];
}
#end
Now, all you have to do is set the barImage property after instantiating your UIToobar. e.g.:
UIToolBar *myToolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, 320, 44)]; //Or whatever frame you want...
myToolbar.barImage = [UIImage imageNamed:#"toolbar-bg.png"];
[self.view addSubView:myToolbar];
[myToolbar release];
And, if you want to change it after it's already onscreen, you can do so by just setting the barImage property to a new UIImage.
Looks like it's been a year since this question was posted, but hopefully this might help someone.
I have a tableview which each row has 4 images. I have implemented a share option which will allow the user to select multiple images. How can I add an overlay or some kind of visual effect to show that the image is selected?
I would like to add some overlay to display that an image is selected, but How would this be done without adding a new set of subview for each thumbnail? And once that is done, how would the selection of the new views be linked back to the images behind them so that they can be added to an array?
Or is there an easier way to do this?
Thanks
Depending on how you're implementing this grid view, it might make sense to track all of the selecting and deselecting at that level.
As for the overlay, the quick and dirty way is to subclass UIImageView, add a BOOL property called selected. Then you can override the setter for selected and handle showing or hiding your overlay view.
Here's how I would setup my subclass. First the interface:
#interface SelectableImageView : UIImageView
#property (nonatomic, assign, getter = isSelected) BOOL selected;
#end
and the implementation...
#interface SelectableImageView ()
#property (nonatomic, retain) UIView *overlayView;
#end
#implementation SelectableImageView
#synthesize selected;
#synthesize overlayView;
- (id)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
overlayView = [[UIView alloc] initWithFrame:frame];
overlayView.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.75];
overlayView.hidden = YES;
[self addSubview:overlayView];
}
return self;
}
- (void)setSelected:(BOOL)flag
{
selected = flag;
self.overlayView.hidden = !flag;
}
- (void)dealloc
{
[overlayView release], self.overlayView = nil;
[super dealloc];
}
#end
I have been trying to set a UIImageView background color (see below) in awakeFromNib
[imageView setBackgroundColor:[UIColor colorWithRed:0 green:0 blue:0 alpha:1.0]];
When it did not work, I realised that its probably because the view has not loaded yet and I should move the color change to viewDidLoad.
Can I just verify that I have this right?
gary
EDIT_002:
I have just started a fresh project to check this from a clean start. I setup the view the same as I always do. The results are that the controls are indeed set to (null) in the awakeFromNib. Here is what I have:
CODE:
#interface iPhone_TEST_AwakeFromNibViewController : UIViewController {
UILabel *myLabel;
UIImageView *myView;
}
#property(nonatomic, retain)IBOutlet UILabel *myLabel;
#property(nonatomic, retain)IBOutlet UIImageView *myView;
#end
.
#synthesize myLabel;
#synthesize myView;
-(void)awakeFromNib {
NSLog(#"awakeFromNib ...");
NSLog(#"myLabel: %#", [myLabel class]);
NSLog(#"myView : %#", [myView class]);
//[myLabel setText:#"AWAKE"];
[super awakeFromNib];
}
-(void)viewDidLoad {
NSLog(#"viewDidLoad ...");
NSLog(#"myLabel: %#", [myLabel class]);
NSLog(#"myView : %#", [myView class]);
//[myLabel setText:#"VIEW"];
[super viewDidLoad];
}
OUTPUT:
awakeFromNib ...
myLabel: (null)
myView : (null)
viewDidLoad ...
myLabel: UILabel
myLabel: UIImageView
I would be interested to know if this should work, from the docs it looks like it should, but given the way I usually set things up I can't quite understand why it does not in this case.
One more answer :-) It looks like you’re getting this behaviour because the controller loads the views lazily. The view is not loaded immediately, it gets loaded the first time somebody calls the view accessor. Therefore at the time you recieve awakeFromNib the NIB loading process is done, but not for the objects inside your views. See this code:
#property(retain) IBOutlet UILabel *foo;
#synthesize foo;
- (void) awakeFromNib
{
NSLog(#"#1: %i", !!foo);
[super awakeFromNib];
NSLog(#"#2: %i", !!foo);
}
- (void) viewDidLoad
{
NSLog(#"#3: %i", !!foo);
}
This logs:
#1: 0
#2: 0
#3: 1
But if you force-load the view:
- (void) awakeFromNib
{
NSLog(#"#1: %i", !!foo);
[super awakeFromNib];
[self view]; // forces view load
NSLog(#"#2: %i", !!foo);
}
The log changes into this:
#1: 0
#3: 1
#2: 1
I believe your call to super needs to be the first line in the awakeFromNib method, otherwise the elements won't be setup yet.
-(void)awakeFromNib {
[super awakeFromNib];
[imageView setBackgroundColor:[UIColor colorWithRed:0 green:0 blue:0 alpha:1.0]];
[testLabel setText:#"Pants ..."];
}
I know, this post is a bit older, but I recently had a similar problem and would like to share its solution with you.
Having subclassed NSTextView, I wanted to display the row colors in alternating orders. To be able to alter the colors from outside, I added two instance vars to my subclass, XNSStripedTableView:
#interface XNSStripedTableView : NSTableView {
NSColor *pColor; // primary color
NSColor *sColor; // secondary color
}
#property (nonatomic, assign) NSColor *pColor;
#property (nonatomic, assign) NSColor *sColor;
#end
Overwriting highlightSelectionInClipRect: does the trick to set the correct color for the respective clipRect.
- (void)highlightSelectionInClipRect:(NSRect)clipRect
{
float rowHeight = [self rowHeight] + [self intercellSpacing].height;
NSRect visibleRect = [self visibleRect];
NSRect highlightRect;
highlightRect.origin = NSMakePoint(NSMinX(visibleRect), (int)(NSMinY(clipRect)/rowHeight)*rowHeight);
highlightRect.size = NSMakeSize(NSWidth(visibleRect), rowHeight - [self intercellSpacing].height);
while (NSMinY(highlightRect) < NSMaxY(clipRect)) {
NSRect clippedHighlightRect = NSIntersectionRect(highlightRect, clipRect);
int row = (int) ((NSMinY(highlightRect)+rowHeight/2.0)/rowHeight);
NSColor *rowColor = (0 == row % 2) ? sColor : pColor;
[rowColor set];
NSRectFill(clippedHighlightRect);
highlightRect.origin.y += rowHeight;
}
[super highlightSelectionInClipRect: clipRect];
}
The only problem now is, where to set the initial values for pColor and sColor? I tried awakeFromNib:, but this would cause the debugger to come up with an error. So I dug into the problem with NSLog: and found an easy but viable solution: setting the initial values in viewWillDraw:. As the objects are not created calling the method the first time, I had to check for nil.
- (void)viewWillDraw {
if ( pColor == nil )
pColor = [[NSColor colorWithSRGBRed:0.33 green:0.33 blue:0 alpha:1] retain];
if ( sColor == nil )
sColor = [[NSColor colorWithSRGBRed:0.66 green:0.66 blue:0 alpha:1] retain];
}
I do think this solution is quite nice :-) although one could reselect the names of pColor and sColor could be adjusted to be more "human readable".
Are you sure the objects are not nil? NSAssert or NSParameterAssert are your friends:
-(void) awakeFromNib {
NSParameterAssert(imageView);
NSParameterAssert(testLabel);
NSLog(#"awakeFromNib ...");
[imageView setBackgroundColor:[UIColor colorWithRed:0 green:0 blue:0 alpha:1.0]];
[testLabel setText:#"Pants ..."];
[super awakeFromNib];
}
If the objects are really initialized, try to log their address and make sure that the instances that appear in viewDidLoad are the same as those in awakeFromNib:
- (void) awakeFromNib {
NSLog(#"test label #1: %#", testLabel);
}
- (void) viewDidLoad {
NSLog(#"test label #2: %#", testLabel);
}
If the numbers are the same, you can create a category to set a breakpoint on setBackgroundColor and peek in the stack trace to see what’s going on:
#implementation UIImageView (Patch)
- (void) setBackgroundColor: (UIColor*) whatever {
NSLog(#"Set a breakpoint here.");
}
#end
You can do the same trick using a custom subclass:
#interface PeekingView : UIImageView {}
#end
#implementation PeekingView
- (void) setBackgroundColor: (UIColor*) whatever {
NSLog(#"Set a breakpoint here.");
[super setBackgroundColor:whatever];
}
#end
Now you’ll set your UIViewObject to be of class PeekingView in the Interface Builder and you’ll know when anybody tries to set the background. This should catch the case where somebody overwrites the background changes after you initialize the view in awakeFromNib.
But I presume that the problem will be much more simple, ie. imageView is most probably nil.
In case you're using a UIView subclass instead of a UIViewController subclass, you can override loadView method:
- (void)loadView
{
[super loadView];
//IBOutlets are not nil here.
}
I need some guidance on creating a UITableViewCell that has an image on the left which can be toggled. The image should be tappable and act as a toggle (checkbox).
My parts I'm struggling with are:
How do I detect taps on the image and handle those differently to didSelectRowAtIndexPath?
How do I change the image without performing a [tableView reloadData]?
It's actually pretty easy.
Just create a new subclass of UIControl and put it all in there (no need for a separate controller.) Let's call it ToggleImageControl.
#interface ToggleImageControl : UIControl
{
BOOL selected;
UIImageView *imageView;
UIImage *normalImage;
UIImage *selectedImage;
}
Create a ToggleImageControl for each cell, and add it at the appropriate position.
ToggleImageControl *toggleControl = [[ToggleImageControl alloc] initWithFrame: <frame>];
toggleControl.tag = indexPath.row; // for reference in notifications.
[cell.contentView addSubview: toggleControl];
Add a UIImageView to contain the image. Add a target for the touch event.
- (void) viewDidLoad
{
normalImage = [UIImage imageNamed: #"normal.png"];
selectedImage = [UIImage imageNamed: #"selected.png"];
imageView = [[UIImageView alloc] initWithImage: normalImage];
// set imageView frame
[self.view addSubview: imageView];
[self addTarget: self action: #selector(toggleImage) forControlEvents: UIControlEventTouchUpInside];
}
Set the UIImageView's image property to update the image; that will trigger the redraw without side-effects.
- (void) toggleImage
{
selected = !selected;
imageView.image = (selected ? selectedImage : normalImage);
// Use NSNotification or other method to notify data model about state change.
// Notification example:
NSDictionary *dict = [NSDictionary dictionaryWithObject: [NSNumber numberWithInt: self.tag forKey: #"CellCheckToggled"];
[[NSNotificationCenter defaultCenter] postNotificationName: #"CellCheckToggled" object: self userInfo: dict];
}
You will obviously need to massage some stuff. You probably want to pass in the two image names to make it more reusable, and also I'd recommend specifying the notification name string from outside the object as well (assuming you are using the notification method.)
Here's an implementation of the "override touchesBegan:" approach I'm using that is simple and seems to work well.
Just include this class in your project and create and configure a TouchIconTableViewCell instead of a UITableView cell in your tableView:cellForRowAtIndexPath: method.
TouchIconTableViewCell.h:
#import <UIKit/UIKit.h>
#class TouchIconTableViewCell;
#protocol TouchIconTableViewCellDelegate<NSObject>
#required
- (void)tableViewCellIconTouched:(TouchIconTableViewCell *)cell indexPath:(NSIndexPath *)indexPath;
#end
#interface TouchIconTableViewCell : UITableViewCell {
id<TouchIconTableViewCellDelegate> touchIconDelegate; // note: not retained
NSIndexPath *touchIconIndexPath;
}
#property (nonatomic, assign) id<TouchIconTableViewCellDelegate> touchIconDelegate;
#property (nonatomic, retain) NSIndexPath *touchIconIndexPath;
#end
TouchIconTableViewCell.m:
#import "TouchIconTableViewCell.h"
#implementation TouchIconTableViewCell
#synthesize touchIconDelegate;
#synthesize touchIconIndexPath;
- (void)dealloc {
[touchIconIndexPath release];
[super dealloc];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint location = [((UITouch *)[touches anyObject]) locationInView:self];
if (CGRectContainsPoint(self.imageView.frame, location)) {
[self.touchIconDelegate tableViewCellIconTouched:self indexPath:self.touchIconIndexPath];
return;
}
[super touchesBegan:touches withEvent:event];
}
#end
Each time you create or re-use the cell, set the touchIconDelegate and touchIconIndexPath properties. When your icon is touched, the delegate will be invoked. Then you can update the icon or whatever.
So the "..obviously need to massage some stuff.." comment means "...this code doesn't work...".
So
- (void) viewDidLoad
should be
- (id)initWithFrame:(CGRect)frame
{
if ( self = [super initWithFrame: frame] ){
normalImage = [UIImage imageNamed: #"toggleImageNormal.png"];
selectedImage = [UIImage imageNamed: #"toggleImageSelected.png"];
imageView = [[UIImageView alloc] initWithImage: normalImage];
// set imageView frame
[self addSubview: imageView];
[self addTarget: self action: #selector(toggleImage) forControlEvents: UIControlEventTouchDown];
}
return self;
}
As - (void) viewDidLoad never gets called.
It seems that Apple uploaded "TableMultiSelect" as sample codes on iOS Developer Program since 2011-10-12.
Multiple selection in edit mode can be enabled by this code.
self.tableView.allowsMultipleSelectionDuringEditing = YES;
http://developer.apple.com/library/ios/#samplecode/TableMultiSelect/Introduction/Intro.html
Though it can be used only from iOS5.
Several hours I could not find this sample code in Stack Overflow, so I added this info to this post.
There's an even EASIER way to do this, if you override touchesBegan: you need to do an if statement to decide if it's within the check marks proximity, if it's not call [super touchesBegan:touches withEvent:event] and it will act as though it was selected.
I do something similar (starring a favorite) like this, but I think you're demanding a lot from the thumbs of iPhone users with the cell directing people to another view. First of all, I would check out the detail disclosure option on cells. One of the options is a pre-made arrow button that you can attach to. Your call.
You might be able to get away with catching the didSelectRowAtIndexPath event and then doing some other logic instead of redirecting if the touch was on your checkbox, although I don't know how you would get the position. This means you might need to find a way to get ahold of the touch event before it calls didSelectRowAtIndex path, which I'm not quite sure how to do. Have you worked with handling touchesBegan and the like yet?