I have subclassed UIImageView and tried to override drawRect so I could draw on top of the image using Quartz 2D. I know this is a dumb newbie question, but I'm not seeing what I did wrong. Here's the interface:
#import <UIKit/UIKit.h>
#interface UIImageViewCustom : UIImageView {
}
- (void)drawRect:(CGRect)rect;
#end
And the implementation:
#import "UIImageViewCustom.h"
#implementation UIImageViewCustom
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
}
return self;
}
- (void)drawRect:(CGRect)rect {
// do stuff
}
- (void)dealloc {
[super dealloc];
}
#end
I set a breakpoint on drawRect and it never hits, leading me to think it never gets called at all. Isn't it supposed to be called when the view first loads? Have I incorrectly overridden it?
It'll only get called if the view is visible, and dirty. Maybe the problem is in the code that creates the view, or in your Nib, if that's how you're creating it?
You'll also sometimes see breakpoints failing to get set properly if you're trying to debug a "Release" build.
I somehow missed the first time that you're subclassing UIImageView. From the docs:
Special Considerations
The UIImageView class is optimized to
draw its images to the display.
UIImageView will not call drawRect: in a
subclass. If your subclass needs
custom drawing code, it is recommended
you use UIView as the base class.
So there you have it. Given how easy it is to draw an image into your view using [UIImage drawInRect:], or by using CALayer, there's probably no good reason to subclass UIImageView anyway.
Try to add
Edit:
- (id)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
[self setClearsContextBeforeDrawing:YES];//add this line also
}
return self;
}
- (void)setNeedsDisplay{
[self setNeedsDisplayInRect:self.frame];
}
into your code.
hope this helps.
Thanks,
madhup
Not directly answering your question, but may solve your problem:
Why do this with a subclass of UIImageView? Subclassing can always be problematic, especially for classes that aren't designed to be subclassed--and I bet UIImageView isn't. If you just want to draw stuff on top of an image, then create a view with a transparent background, draw whatever you want, and place it directly over the image.
Depending on your specific case, it could make sense to use a UIButton with a background image instead of a UIImageView. You could even set userInteractionEnabled to NO and the result would be indistinguishable from a UIImageView.
In this case your UIButton subclass would simply include:
- (void)drawRect:(CGRect)rect
{
if (kSomethingSpecial) {
[self setBackgroundImage:[UIImage imageNamed:#"RedBackground.png"]
forState:UIControlStateNormal];
}
}
Disclaimer - I would only recommend this if you have plans to use the image as a button at least some of the time. I can't wholeheartedly endorse using a UIButton as a workaround for this drawRect behaviour in UIImageView, as I'm sure Apple has its reasons for writing their API this way, like Mark referenced.
Related
I have a custom UIView that I'm using for the UITableView header. I'm creating it in code. (I'm basically following the TVAnimationGestures from WWDC 2010). For UI elements that are some offset from the left hand side, it looks good in either orientation. However, if I want a UILabel that is offset from the right hand size, I'm not sure what to do. If I were in IB, I would use the springs/struts. But not sure how to do that in code. Thanks.
springs/struts in IB is a GUI interface to autoresizingMask:
http://developer.apple.com/library/ios/#documentation/uikit/reference/uiview_class/UIView/UIView.html#//apple_ref/doc/uid/TP40006816-CH3-SW6
For right justification, You want to position the label with the correct offset when you first create it and set autoresizingMask to UIViewAutoresizingFlexibleLeftMargin (and any other that you want.)
A simple way to do this is to implement a method in the view which will place it's subviews according to the UIInterfaceOrientation.
You can then call this method from the UIViewController when the rotation is detected.
For example:
In YourView.h
#interface YourView : UIView {
}
// Public API
- (void)placeWidgetsForInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation;
#end
In YourView.m
#import "YourView.h"
#implementation YourView
...
#pragma mark -
#pragma mark Public API
- (void)placeWidgetsForInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Change frames of subviews according to orientation in here
}
#end
Then in YourController.m
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
YourView *theView = (YourView *) self.view; // Or where ever your view is
[theView placeWidgetsForInterfaceOrientation:toInterfaceOrientation];
}
My problem is that i want to detect the zoom scale of an UIWebView, i have tried searching it but did not come out with a proper answer.Any help is appreciated......
Well although the UIWebView doesn't have a zoomScale property, UIScrollView does!
So we just scan it's subView's for the scrollView everything sits in and get it that way.
Here's a little (1 method) category that will allow you to get the scale by calling [webView zoomScale].
UIWebView+zoom.h file
#interface UIWebView (zoom)
-(float)zoomScale;
#end
UIWebView+zoom.m file
#implementation UIWebView (zoom)
-(float)zoomScale{
UIScrollView *webViewContentView;
for (UIView *checkView in [self subviews] ) {
if ([checkView isKindOfClass:[UIScrollView class]]) {
webViewContentView = (UIScrollView*)checkView;
break;
}
}
return webViewContentView.zoomScale;
}
#end
UIScrollView Class Reference
UIWebView's View Hierarchy (Don't rely on it though, always scan the webView to avoid code breaking when apple makes changes to iOS)
NOTE: This code should work but has been written in the reply box so hasn't been tested.
I have my app on iPhone with a UIViewController with some stuff inside.. image, textbox, etc...
is there a way to draw a line or something like this using opengl directly inside the UIViewController?
thanks in advance
Are you sure about the OpenGL?
I believe it will be impossible to use OpenGL above regular views.
You might add a custom view (inherit from UIView) above all the other views and draw anything you want on that view.
This view should have a transparent background color.
In addition, if you want to interact with the other views (tap buttons, edit text views, scroll etc.) then you will have to implement the hitTest method in your custom view in order to pass the touch event to the views that are located under this one...
EDIT: Don't mess with the hitTest. Just uncheck the User Interaction Enabled in the XIB...
EDIT: Code sample:
#interface TransparentDrawingView : UIView {
CGPoint fromPoint;
CGPoint toPoint;
}
- (void)drawLineFrom:(CGPoint)from to:(CGPoint)to;
#end
#implementation TransparentDrawingView
- (void)initObject {
// Initialization code
[super setBackgroundColor:[UIColor clearColor]];
}
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// Initialization code
[self initObject];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aCoder {
if (self = [super initWithCoder:aCoder]) {
// Initialization code
[self initObject];
}
return self;
}
- (void)drawRect:(CGRect)rect {
// Drawing code
// Draw a line from 'fromPoint' to 'toPoint'
}
- (void)drawLineFrom:(CGPoint)from to:(CGPoint)to {
fromPoint = from;
toPoint = to;
// Refresh
[self setNeedsDisplay];
}
#end
First I think you should know responsibilities of UIView and UIViewController.
UIView is mainly responsible for drawing (override touchBegin, ToucheMove, etc.), animating, manage subviews, and handle event; UIViewController is mainly responsible for loading, unloading views etc.
So, you should 'draw' this line at your customized view (UIView), not view controller.
Second: If you only need to display some simple shapes or lines. I suggest you use UI controls and images. Even 'drawRect' is not recommended since it will cause more resources used. Surely OpenGL need much resources.
The answer is quite simple, you have to create a new class extending UIView, and override the drawRect function in order to draw (with Quartz...) your line. Use this method if you want gradient background as well.
I am also developping with Adobe Flex 4, and I notice that iPhone SDK has no Skin and Layout "patterns" like Flex 4 does (for instance, UIView could have a Skin (in a separate file..) and a Layout (horizontal, Vertical, Tile....)), for me that is a terrible lack!
Its something that the open source library Three20 tries to bring to the iPhone SDK.
Trying to draw a shadow using code from this question: How do I draw a shadow under a UIView?
I implement it in a UIView subclass as discussed, but when I try and use it using UIView *shadow = [[ShadowView alloc]initWithFrame:CGRectMake(100,100,100,100)]; I get only a black square, rather than something resembling shadow.
Am I missing something here?
I know this an ancient question, but I came across it via google as I was trying to do the same thing. So I thought I would post incase anyone else has the same problem. I finally discovered a fix after reading this tutorial: http://www.raywenderlich.com/2134/core-graphics-101-glossy-buttons
You need to either uncheck Opaque, and set the Background to Clear Color in IB.
OR as shown in the tutorial set them in initWithCoder
-(id) initWithCoder:(NSCoder *)aDecoder {
if ((self = [super initWithCoder:aDecoder])) {
self.opaque = NO;
self.backgroundColor = [UIColor clearColor];
}
return self;
}
Yup, you probably need to explicitly add an #import to the top of your class. I've had the same issue before and that fixed it. (Can't exactly explain why though)
I have a fullscreen background image that is tiled, i.e. it has to be reproduced a few times horizontally and vertically in order to make a big one. Like in the browsers on ugly home pages ;)
Is UIImageView my friend for this?
If I understand your question correctly you can use colorWithPatternImage: on UIColor then set the background color on a UIView.
If you must use a UIImageView you can do the same but whatever image you place in the image view will draw in front of the tiled image.
To get alpha to work with pattern image, make sure you have the following set:
view.backgroundColor = [UIColor colorWithPatternImage:aImage];
view.layer.opaque = NO;
In WWDC 2018 video session 219 - Image and Graphics Best Practices, Apple engineer explicitly recommends not to use the pattern color for tiling backgrounds:
I recommend not using patterned colors with a background color property on UIView. Instead, create a UIImageView. Assign your image to that image view. And use the functions on UIImageView to set your tiling parameters appropriately.
So the best and simplest way to create a tiled background would be like this:
imageView.image = image.resizableImage(withCapInsets: .zero, resizingMode: .tile)
Or even simpler, if you use asset catalog – select your pattern image asset and, in the Attributes inspector, enable Slicing (Horizontal/Vertical or both), set the insets to zero, and width/height to the dimensions of your image:
then simply assign this image to your image view (Interface Builder works, too), just don't forget to set the UIImageView's contentMode to .scaleToFill.
For years I used Bill Dudney's approach, but iOS 6 has a much better solution. And ... today I found a way to make this work on old versions of iOS too.
create the new class "UIImage+Tileable" (copy/paste source below)
import this in any class where you want a UIImageView with tileable image. It's a category, so it "upgrades" all your UIImage's into tileable ones, using standard Apple calls
when you want a "tiling" version of an image, call: "image = [image imageResizingModeTile]"
UIImage+Tileable.h
#import <UIKit/UIKit.h>
#interface UIImage (Tileable)
-(UIImage*) imageResizingModeTile;
#end
UIImage+Tileable.m
#import "UIImage+Tileable.h"
#implementation UIImage (Tileable)
-(UIImage*) imageResizingModeTile
{
float iOSVersion = [[[UIDevice currentDevice] systemVersion] floatValue];
if( iOSVersion >= 6.0f )
{
return [self resizableImageWithCapInsets:UIEdgeInsetsZero resizingMode:UIImageResizingModeTile];
}
else
{
return [self resizableImageWithCapInsets:UIEdgeInsetsZero];
}
}
#end
I use a variation of #Rivera's solution:
Put the following in a UIView extension:
- (void)setColorPattern:(NSString *)imageName
{
[self setBackgroundColor:[UIColor colorWithPatternImage:[UIImage imageNamed:imageName]]];
}
Then you can set the background pattern in the storyboard/xib file:
As I really like Interface Builder I created this UIImageView subclass to apply tiled backgrounds:
#interface PETiledImageView : UIImageView
#end
#implementation PETiledImageView
- (void)awakeFromNib
{
[super awakeFromNib];
UIImage * imageToTile = self.image;
self.image = nil;
UIColor * tiledColor = [UIColor colorWithPatternImage:imageToTile];
self.backgroundColor = tiledColor;
}
#end
I tried overriding setImage: but it seems IB doesn't call it when decoding a Nib file.
Swift version of Daniel T's solution. You still need to set the keyPath value in IB. Of course you could be more careful unwrapping the Optional UIImage.
extension UIView {
var colorPattern:String {
get {
return "" // Not useful here.
}
set {
self.backgroundColor = UIColor(patternImage: UIImage(named:newValue)!)
}
}
}