UIImage Resize drawRect Filling Background with Black - objective-c

I have a class called MNTRectangle which is a subclass of UIImage. I have overridden the drawRect method of this class to draw a border on the image (using its frame). I have it so when the user starts a panning/dragging gesture on a class called DocumentView (subclass of UIView) it adds an instance of MNTRectangle as a subview to the instance of DocumentView. As the user continues to drag the MNTRectangle is resized. The problem is that the MNTRectangle is appearing as solid black in the end, I have tried clearing the graphics context as well as saving the context before drawing the border and restoring the context after drawing the border. No matter what I seem to try I cannot get the MNTRectangle to clear and just show the border on resize.
Here is the code for my MNTRectangle class:
#implementation MNTRectangle
- (id)init
{
self = [super init];
if (self)
{
[self setup];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
{
[self setup];
}
return self;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
[self setup];
}
return self;
}
- (void)setup
{
[self setBackgroundColor:[UIColor clearColor]];
}
- (void)drawRect:(CGRect)rect
{
// Get graphics context
CGContextRef context = UIGraphicsGetCurrentContext();
// CGContextSaveGState(context);
// CGContextClearRect(context, rect);
// Draw border
CGContextSetLineWidth(context, 4.0);
[[UIColor blackColor] setStroke];
CGContextStrokeRect(context, rect);
// CGContextRestoreGState(context);
}
Here is the code in DocumentView for the panning/dragging handling on the UIView:
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
_panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
}
return self;
}
- (void)handlePan:(UIPanGestureRecognizer *)sender
{
if ([sender state] == UIGestureRecognizerStateBegan)
{
_startPanPoint = [sender locationInView:self];
MNTRectangle *rectangle = [[MNTRectangle alloc] initWithFrame:CGRectMake(_startPanPoint.x, _startPanPoint.y, 1, 1)];
[_objects addObject:rectangle];
_currentPanObject = rectangle;
[self addSubview:rectangle];
}
else if ([sender state] == UIGestureRecognizerStateChanged)
{
CGPoint endPanPoint = [sender locationInView:self];
float height = fabsf(endPanPoint.y - _startPanPoint.y);
float width = fabsf(endPanPoint.x - _startPanPoint.x);
float x = MIN(_startPanPoint.x, endPanPoint.x);
float y = MIN(_startPanPoint.y, endPanPoint.y);
MNTRectangle *rectangle = (MNTRectangle *)_currentPanObject;
[rectangle setFrame:CGRectMake(x, y, width, height)];
}
else if ([sender state] == UIGestureRecognizerStateEnded)
{
CGPoint endPanPoint = [sender locationInView:self];
float height = fabsf(endPanPoint.y - _startPanPoint.y);
float width = fabsf(endPanPoint.x - _startPanPoint.x);
float x = MIN(_startPanPoint.x, endPanPoint.x);
float y = MIN(_startPanPoint.y, endPanPoint.y);
MNTRectangle *rectangle = (MNTRectangle *)_currentPanObject;
[rectangle setFrame:CGRectMake(x, y, width, height)];
}
}
Any help with this would be greatly appreciated.

I finally figured it out. In my handlePan: method after I set the frame of the rectangle I was missing [rectangle setNeedsDisplay];.
Now my DocumentView code looks like this:
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
_panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
}
return self;
}
- (void)handlePan:(UIPanGestureRecognizer *)sender
{
if ([sender state] == UIGestureRecognizerStateBegan)
{
_startPanPoint = [sender locationInView:self];
MNTRectangle *rectangle = [[MNTRectangle alloc] initWithFrame:CGRectMake(_startPanPoint.x, _startPanPoint.y, 1, 1)];
[_objects addObject:rectangle];
_currentPanObject = rectangle;
[self addSubview:rectangle];
}
else if ([sender state] == UIGestureRecognizerStateChanged)
{
CGPoint endPanPoint = [sender locationInView:self];
float height = fabsf(endPanPoint.y - _startPanPoint.y);
float width = fabsf(endPanPoint.x - _startPanPoint.x);
float x = MIN(_startPanPoint.x, endPanPoint.x);
float y = MIN(_startPanPoint.y, endPanPoint.y);
MNTRectangle *rectangle = (MNTRectangle *)_currentPanObject;
[rectangle setFrame:CGRectMake(x, y, width, height)];
[rectangle setNeedsDisplay];
}
else if ([sender state] == UIGestureRecognizerStateEnded)
{
CGPoint endPanPoint = [sender locationInView:self];
float height = fabsf(endPanPoint.y - _startPanPoint.y);
float width = fabsf(endPanPoint.x - _startPanPoint.x);
float x = MIN(_startPanPoint.x, endPanPoint.x);
float y = MIN(_startPanPoint.y, endPanPoint.y);
MNTRectangle *rectangle = (MNTRectangle *)_currentPanObject;
[rectangle setFrame:CGRectMake(x, y, width, height)];
[rectangle setNeedsDisplay];
}
}

You're gonna want to call [super drawRect:rect]

To clarify for anyone reading, calling [rectangle setNeedsDisplay]; is needed because you're overriding drawRect: and thus this subclass of UIView, MNTRectangle, needs to be told to manually redraw itself after it's internal variables have been changed.
This function is called automatically on standard objects that inherit from UIView, but since you've created a custom object, you'll need to call it manually if you have some special behavior in MNTRectangle that requires a redraw.
Further documentation here.

Related

Drawing leaves trails when resizing window

I am trying to draw a box in custom view. I have the points for the corners of the box and they are scaled. If I put a small rectangle at each vertex on the box and the resize the window it works perfectly. If I use [path stroke] to draw the lines between the vertices and then resize the window, I end up with a mess as the box is redrawn as the window gets resized, so instead of a single box in a resized window I get all the boxes that were redrawn. I have not found a way to clear the custom view of all the intermediary drawings. Any help would be appreciated. Also, I am really new at this.
#import <Cocoa/Cocoa.h>
#interface PointDisplay : NSView
{
NSBezierPath *pathForLine;
NSMutableArray *pointList;
float originalWidth;
float originalHeight;
}
#end
import "PointDisplay.h"
#implementation PointDisplay
- (id)initWithFrame:(NSRect)frameRect
{
self = [super initWithFrame:frameRect];
if (self)
{
int opts = (NSTrackingActiveAlways | NSTrackingInVisibleRect | NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved);
NSTrackingArea *area = [[NSTrackingArea alloc] initWithRect:[self bounds]
options:opts
owner:self
userInfo:nil];
[self addTrackingArea:area];
pathForLine = [[NSBezierPath alloc] init];
pointList = [[NSMutableArray alloc] init];
NSRect originalRect = [self bounds];
originalWidth = originalRect.size.width;
originalHeight = originalRect.size.height;
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
[super drawRect:dirtyRect];
NSRect myRect = [self bounds];
float widthNow = myRect.size.width;
float heightNow = myRect.size.height;
[[NSColor greenColor] set];
[NSBezierPath fillRect:myRect];
int rectWidth = 10, rectHeight = 10;
float lineWidth = 3.0;
if (pointList.count != 0)
{
NSPoint myPoint = [pointList[0] locationInWindow];
myPoint = [self convertPoint:myPoint fromView:nil];
myPoint.x = (myPoint.x - rectWidth / 2) * widthNow / originalWidth;
myPoint.y = (myPoint.y - rectHeight / 2) * heightNow / originalHeight;
[pathForLine moveToPoint:myPoint];
NSRect anotherRect = NSMakeRect(myPoint.x, myPoint.y, rectWidth, rectHeight);
[[NSColor redColor] set];
[pathForLine setLineWidth:lineWidth + .5 * ((int)lineWidth % 2)];
//[[NSBezierPath bezierPathWithRect:anotherRect] stroke];
for (int i = 1; i < pointList.count; i++)
{
myPoint = [pointList[i] locationInWindow];
myPoint = [self convertPoint:myPoint fromView:nil];
myPoint.x = (myPoint.x - 5) * widthNow / originalWidth;
myPoint.y = (myPoint.y - 5) * heightNow / originalHeight;
anotherRect = NSMakeRect(myPoint.x, myPoint.y, 10, 10);
[pathForLine lineToPoint:myPoint];
[pathForLine stroke];
//[[NSBezierPath bezierPathWithRect:anotherRect] fill];
}
}
}
#pragma mark Mouse events
- (void)mouseUp:(NSEvent *)theEvent
{
[pointList addObject:theEvent];
[self setNeedsDisplay:YES];
}
- (void)mouseExited:(NSEvent *)theEvent
{
[pathForLine closePath];
[pathForLine stroke];
[self setNeedsDisplay:YES];
}
#end

get CGRect location in superview objective-c

I want to print out the x coordinate of a CGRect. The x,y coordinates of the rect is set to where the user touches, like this:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches]anyObject];
startPoint = [touch locationInView:self];
}
- (void)drawRect:(CGRect)rect
{
ctx = UIGraphicsGetCurrentContext();
jrect = CGRectMake(startPoint.x, startPoint.y, 100, 100);
CGContextAddRect(ctx, jrect);
CGContextFillPath(ctx);
}
I could just print out the startPoint but if I would print out the CGRect's coordinate I tried to do this:
int jrectX = lroundf(CGRectGetMinX(jrect));
xlabel.text = [NSString stringWithFormat:#"x: %i", jrectX];
But the number it returns doesn't make any sense at all, sometimes they are bigger to the left than to the right. Is there anything wrong with the code?
A CGRect is a struct with four CGFloat properties: x, y, width, height
To print the x value from a CGRect:
[NSString stringWithFormat:#"%f", rect.x]
To print the entire rect, there's a convenience function:
NSStringFromCGRect(rect)
You're having problems above, because you're storing the x value into an int, and then using a float rounding function on it. So it should be:
CGFloat jrectX = CGRectGetMinX(jrect);
. . . unless you're doing rotation transformation, you can just use:
CGFloat jrectX = jrect.origin.x;
DR.h
#import <UIKit/UIKit.h>
#interface DR : UIView
{
CGContextRef ctx;
CGRect jrect;
CGPoint startPoint;
UILabel *xlabel;
}
#end
DR.m file.
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
xlabel=[[UILabel alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
[self addSubview:xlabel];
// Initialization code
}
return self;
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
ctx = UIGraphicsGetCurrentContext();
jrect = CGRectMake(startPoint.x, startPoint.y, 100, 100);
CGContextAddRect(ctx, jrect);
CGContextFillPath(ctx);
// Drawing code
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches]anyObject];
startPoint = [touch locationInView:self];
int jrectX = lroundf(CGRectGetMinX(jrect));
NSLog(#"jrectX --------------");
xlabel.text = [NSString stringWithFormat:#"x: %i", jrectX];
[self setNeedsDisplay];
}
#end
In other viewController use it...
- (void)viewDidLoad
{
DR *drr=[[DR alloc] initWithFrame:CGRectMake(0, 0, 320, 460)];
[drr setBackgroundColor:[UIColor greenColor]];
[self.view addSubview:drr];
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}

Update view from controller (objective c)

I have a situation like this:
If I click on left foot button I want that my view in the second controller show a left foot image, and if I click on right button the view should show right foot image.
In the first controller I wrote this to select right or left:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
RSViewController *vc = [segue destinationViewController];
if ([[segue identifier] isEqualToString:#"Left-Foot"])
[vc setLeft:YES];
else
[vc setLeft:NO];
}
In the second controller I have a method which set a boolean value
- (void)setLeft:(BOOL)left
{
if (left) {
_left = left;
NSLog(#"LEFT UPDATED IN RSVIEWCONTROLLER");
}else {
_left = left;
NSLog(#"RIGHT UPDATED IN RSVIEWCONTROLLER");
}
}
and in the same controller I create my view in viewdidload with my one init:
- (void)viewDidLoad
{
NSLog(#"meeeea");
[super viewDidLoad];
CGRect rect = CGRectMake(0, 0, 100, 100);
self.animatedCircleView = [[RSAnimatedCircleView alloc] initWithFrameFoot:rect foot:YES];
NSLog(#"Viewdidnoload");
}
and in my view (small blue rectangle in the picture):
- (void)setup:(BOOL)left
{
self.leftFoot = left;
}
- (void)setLeftFoot:(BOOL)leftFoot
{
_leftFoot = leftFoot;
}
- (void)awakeFromNib
{
[self setup:_leftFoot];
}
- (id)initWithFrameFoot:(CGRect)frame foot:(BOOL)left
{
self = [super initWithFrame:frame];
[self setup:left];
self.leftFoot = left;
if (self.leftFoot)
NSLog(#"Left == TRUE");
return self;
}
- (RSFootView *)footView
{
CGRect rect = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height);
if (self.leftFoot) {
NSLog(#"Now left is yes");
}else {
NSLog(#"left is no");
}
_footView = [[RSFootView alloc] initWithFrame:rect withLeftFoot:self.leftFoot];
[_footView setBackgroundColor:[UIColor clearColor]];
return _footView;
}
- (void)drawRect:(CGRect)rect
{
if (self.leftFoot)
NSLog(#"left ada");
else
NSLog(#"right masd");
CGFloat x = self.bounds.size.height;
CGFloat y = self.bounds.size.width;
CGFloat radius = x/2 - 15;
CGPoint center = CGPointMake(x/2, y/2);
CGFloat startAngle = DEGREES_TO_RADIANS(START_ANGLE);
CGFloat endAngle = DEGREES_TO_RADIANS(END_ANGLE);
// Drawing the sector
UIBezierPath *sector = [UIBezierPath bezierPathWithArcCenter:center
radius:radius
startAngle:startAngle
endAngle:endAngle
clockwise:YES];
sector.lineWidth = 4;
[[UIColor grayColor] setStroke];
[sector stroke];
[self setBackgroundColor:[UIColor clearColor]];
[sector addClip];
[self addSubview:self.footView];
}
in the method "drawRect" the leftfoot value is always false. I do not know why is like this... can anyone help me?
Thank you
You are setting your view to "left" each time in the following line.
self.animatedCircleView = [[RSAnimatedCircleView alloc]
initWithFrameFoot:rect foot:YES];
This is a typical error that becomes very probable when not giving your variables and methods understandable and intuitive names. Expect this type of error to occur frequently if you do not mend your ways.
I would give your view an enum property that states explicitly what foot it is:
typedef enum {LeftFoot, RightFoot} FootType;
#property (strong, nonatomic) FootType footType;
Also, you should revise your method names. I would completely do away with the initWithFrameFoot method and just set a default foot in your override for initWithFrame. (Leaving it at 0 amounts to LeftFoot.) With #synthesize you also do not need a setter or getter. Delete them.
If you want a dedicated initializer, make sure the variable passed are actually preceded by the proper words.
-(id) initWithFrame:(CGRect)rect andFootType:(FootType)footType {
self = [super initWithFrame:rect];
if (self) {
_footType = footType;
}
return self;
}
After using #synthesize you can set the correct foot in prepareForSegue:
footViewController.footType = rightFoot;

Stretchy UIBezierPath line?

I want to draw a straight line with my finger that automatically sizes based upon how far away I am from the point of origin.
So if I touch the screen in the middle and slide my finger out a line appears to 'stretch' and pivot around the point of orgin as my finger moves on the screen. WHhen I lift my finger. The Destination Point should finalize and create a line. I can drag my finger across the screen and 'Draw' on the screen but that's not what I am wanting to do.
I thought UIBeizerPath moveToPoint would help but it just messes things up.
What am I doing wrong?
- (id)initWithFrame:(CGRect)frame
{
//default line properties
myPath=[[UIBezierPath alloc]init];
myPath.lineCapStyle=kCGLineCapRound;
myPath.miterLimit=0;
myPath.lineWidth=lineWidth;
brushPattern=[UIColor blackColor];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint curPoint = [[touches anyObject] locationInView:self];
lastPoint = curPoint;
[myPath moveToPoint:lastPoint];
[pathArray addObject:myPath];
[self setNeedsDisplay];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint curPoint = [[touches anyObject] locationInView:self];
myPath.lineWidth=lineWidth;
brushPattern=[UIColor redColor]; //red to show it hasn't been added yet.
[myPath moveToPoint:tempPoint];
[self setNeedsDisplay];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint curPoint = [[touches anyObject] locationInView:self];
myPath.lineWidth=lineWidth;
brushPattern=[UIColor blackColor]; //finalize the line with black color
[myPath addLineToPoint:curPoint];
[self setNeedsDisplay];
}
Here's one concept. Draws a line from where you start dragging your finger until where you let go, animating it as you drag your finger around. It does this by making a CAShapeLayer, resetting the path as you move your finger around.
This should demonstrate the basic idea:
- (void)viewDidLoad {
[super viewDidLoad];
UIPanGestureRecognizer *gesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePanGesture:)];
[self.view addGestureRecognizer:gesture];
}
- (CAShapeLayer *)createShapeLayer:(UIView *)view {
CAShapeLayer *shapeLayer = [[CAShapeLayer alloc] init];
shapeLayer.fillColor = [UIColor clearColor].CGColor;
shapeLayer.strokeColor = [UIColor redColor].CGColor;
shapeLayer.lineWidth = 3.0;
[view.layer addSublayer:shapeLayer];
return shapeLayer;
}
- (void)handlePanGesture:(UIPanGestureRecognizer *)gesture {
static CAShapeLayer *shapeLayer;
static CGPoint origin;
if (gesture.state == UIGestureRecognizerStateBegan) {
shapeLayer = [self createShapeLayer:gesture.view];
origin = [gesture locationInView:gesture.view];
} else if (gesture.state == UIGestureRecognizerStateChanged) {
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:origin];
CGPoint location = [gesture locationInView:gesture.view];
[path addLineToPoint:location];
shapeLayer.path = path.CGPath;
} else if (gesture.state == UIGestureRecognizerStateEnded ||
gesture.state == UIGestureRecognizerStateFailed ||
gesture.state == UIGestureRecognizerStateCancelled) {
shapeLayer = nil;
}
}
Or, in Swift 3:
override func viewDidLoad() {
super.viewDidLoad()
let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
view.addGestureRecognizer(pan)
}
private func createShapeLayer(for view: UIView) -> CAShapeLayer {
let shapeLayer = CAShapeLayer()
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.lineWidth = 3.0
view.layer.addSublayer(shapeLayer)
return shapeLayer
}
private var shapeLayer: CAShapeLayer!
private var origin: CGPoint!
func handlePan(_ gesture: UIPanGestureRecognizer) {
if gesture.state == .began {
shapeLayer = createShapeLayer(for: gesture.view!)
origin = gesture.location(in: gesture.view)
} else if gesture.state == .changed {
let path = UIBezierPath()
path.move(to: origin)
path.addLine(to: gesture.location(in: gesture.view))
shapeLayer.path = path.cgPath
} else if gesture.state == .ended || gesture.state == .failed || gesture.state == .cancelled {
shapeLayer = nil
}
}
If you don't use CAShapeLayer, but you want to keep track of previous paths, you'll have to maintain an array for those old paths, and build a path that consists of all of the old paths, perhaps something like:
#interface CustomView ()
#property (nonatomic) CGPoint originPoint;
#property (nonatomic) CGPoint currentPoint;
#property (nonatomic) NSMutableArray *previousPaths;
#end
#implementation CustomView
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self configure];
}
return self;
}
- (id)init {
return [self initWithFrame:CGRectZero];
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self configure];
}
return self;
}
- (void)configure {
_previousPaths = [[NSMutableArray alloc] init];
}
- (void)drawRect:(CGRect)rect {
[[UIColor redColor] setStroke];
UIBezierPath *drawPath = [UIBezierPath bezierPath];
drawPath.lineCapStyle = kCGLineCapRound;
drawPath.miterLimit = 0;
drawPath.lineWidth = 3.0;
for (UIBezierPath *path in self.previousPaths)
[drawPath appendPath:path];
UIBezierPath *path = [self pathForCurrentLine];
if (path)
[drawPath appendPath:path];
[drawPath stroke];
}
- (UIBezierPath *)pathForCurrentLine {
if (CGPointEqualToPoint(self.originPoint, CGPointZero) && CGPointEqualToPoint(self.currentPoint, CGPointZero))
return nil;
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:self.originPoint];
[path addLineToPoint:self.currentPoint];
return path;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self.originPoint = [[touches anyObject] locationInView:self];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
if ([event respondsToSelector:#selector(predictedTouchesForTouch:)]) {
touch = [[event predictedTouchesForTouch:touch] lastObject] ?: touch;
}
self.currentPoint = [touch locationInView:self];
[self setNeedsDisplay];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
self.currentPoint = [[touches anyObject] locationInView:self];
[self.previousPaths addObject:[self pathForCurrentLine]];
self.originPoint = self.currentPoint = CGPointZero;
[self setNeedsDisplay];
}
#end
UIBezierPath is building a path from your instructions. Imagine a pen. When you say, "moveToPoint:" it moves the pen to that point. When you say "lineToPoint:" it puts the pen down and moves it from the current location to the new point. And so on.
To get the effect you desire, you will need to create a new path whenever the touches move, drawing a line from the original point to the current touch position.

Multiple images/buttons staying relative to their position on a zoomable image

I've made a map with UIScrolView, I want to place other small images or buttons onto the map and have them be in a relative position on the map when you zoom in and be able to click on them whenever. So when zoomed out, a button on country A, will still be on Country A when zoomed in, and disappear out of the screen when you scroll out of the countries view whilst zoomed in. How could I go about doing this?
As i can understand, you want to place custom views on your own custom map. And you need to keep the same sizes for views, but they should move when you scroll or zoom imageView.
You have to place views to scrollView's superview and recalculate positions when you zoom or scroll:
CustomMapViewController.h:
#interface CustomMapViewController : UIViewController <UIScrollViewDelegate>
{
UIScrollView *_scrollView;
UIImageView *_mapImageView;
NSArray *_customViews;
}
CustomMapViewController.m:
#import "CustomMapViewController.h"
enum {
kAddContactButton = 1,
kInfoDarkButton,
kInfoLightButton,
kLogoImage,
};
#implementation CustomMapViewController
- (void)dealloc
{
[_scrollView release]; _scrollView = nil;
[_mapImageView release]; _mapImageView = nil;
[_customViews release]; _customViews = nil;
[super dealloc];
}
- (void) loadView
{
[super loadView];
UIImageView *mapImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"map.png"]];
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
scrollView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
scrollView.delegate = self;
scrollView.minimumZoomScale = 0.2;
scrollView.maximumZoomScale = 2.0;
[scrollView addSubview:mapImageView];
scrollView.contentSize = mapImageView.frame.size;
[self.view addSubview:scrollView];
_scrollView = scrollView;
_mapImageView = mapImageView;
// Add custom views
UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeContactAdd];
btn1.tag = kAddContactButton;
[self.view addSubview:btn1];
UIButton *btn2 = [UIButton buttonWithType:UIButtonTypeInfoDark];
btn2.tag = kInfoDarkButton;
[self.view addSubview:btn2];
UIButton *btn3 = [UIButton buttonWithType:UIButtonTypeInfoLight];
btn3.tag = kInfoLightButton;
[self.view addSubview:btn3];
UIImageView *image = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:#"logo.png"]] autorelease];
image.tag = kLogoImage;
[self.view addSubview:image];
_customViews = [[NSArray alloc] initWithObjects:btn1, btn2, btn3, image, nil];
[self _zoomToFit];
}
- (void) _zoomToFit
{
UIScrollView *scrollView = _scrollView;
CGFloat contentWidth = scrollView.contentSize.width;
CGFloat contentHeigth = scrollView.contentSize.height;
CGFloat viewWidth = scrollView.frame.size.width;
CGFloat viewHeight = scrollView.frame.size.height;
CGFloat width = viewWidth / contentWidth;
CGFloat heigth = viewHeight / contentHeigth;
CGFloat scale = MIN(width, heigth); // to fit
// CGFloat scale = MAX(width, heigth); // to fill
// May be should add something like this
if ( scale < _scrollView.minimumZoomScale ) {
_scrollView.minimumZoomScale = scale;
} else if ( scale > _scrollView.maximumZoomScale ) {
_scrollView.maximumZoomScale = scale;
}
_scrollView.zoomScale = scale;
}
////////////////////////////////////////////////////////////////////////////////////////
#pragma mark - Positions
- (void) _updatePositionForViews:(NSArray *)views
{
CGFloat scale = _scrollView.zoomScale;
CGPoint contentOffset = _scrollView.contentOffset;
for ( UIView *view in views ) {
CGPoint basePosition = [self _basePositionForView:view];
[self _updatePositionForView:view scale:scale basePosition:basePosition offset:contentOffset];
}
}
- (CGPoint) _basePositionForView:(UIView *)view
{
switch (view.tag) {
case kAddContactButton:
return CGPointMake(50.0, 50.0);
case kInfoDarkButton:
return CGPointMake(250.0, 250.0);
case kInfoLightButton:
return CGPointMake(450.0, 250.0);
case kLogoImage:
return CGPointMake(650.0, 450.0);
default:
return CGPointZero;
}
}
- (void) _updatePositionForView:(UIView *)view scale:(CGFloat)scale basePosition:(CGPoint)basePosition offset:(CGPoint)offset;
{
CGPoint position;
position.x = (basePosition.x * scale) - offset.x;
position.y = (basePosition.y * scale) - offset.y;
CGRect frame = view.frame;
frame.origin = position;
view.frame = frame;
}
//////////////////////////////////////////////////////////////////////////////////////
#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidZoom:(UIScrollView *)scrollView
{
[self _updatePositionForViews:_customViews];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
[self _updatePositionForViews:_customViews];
}
- (UIView *) viewForZoomingInScrollView:(UIScrollView *)scrollView;
{
return _mapImageView;
}
#end