iOS Custom MKAnnotation object resembling MKUserLocation animation - iphone

I want to drop MKAnnotationView pins on a map and have the pins be animated much like the MKUserLocation animates (the blue dot with circular waves coming out)
How do you achieve this type of animation? What steps do I need to take?
Thanks!

Animate it like any UIView!
subclass MKAnnotationView for custom content. don't go via repeated addAnnotation calls to animate it! that is wrong
you can treat the anotationView just like any other view..
here is some code that has a blinking animation of images -- this code should get you started.
(not pretty but the exact stuff you will need for your case!)
#import "DDViewController.h"
#import <MapKit/MapKit.h>
#import <QuartzCore/QuartzCore.h>
#interface DummyAnnotation : NSObject<MKAnnotation>
#end
#implementation DummyAnnotation
- (CLLocationCoordinate2D)coordinate { return CLLocationCoordinate2DMake(51, 10); }
- (NSString *)title { return #"Dummy"; }
#end
#interface DummyAnnotationView : MKAnnotationView
#end
#implementation DummyAnnotationView
- (id)initWithAnnotation:(id <MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectZero];
imageView.autoresizingMask = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth;
imageView.animationImages = #[[UIImage imageNamed:#"1.gif"],[UIImage imageNamed:#"2.gif"]];
imageView.animationDuration = 5;
[imageView startAnimating];
[self addSubview:imageView];
return self;
}
#end
#interface DDViewController() <MKMapViewDelegate>
#end
#implementation DDViewController
- (MKMapView*)mapView {
return (MKMapView*)self.view;
}
- (void)viewDidLoad
{
[super viewDidLoad];
//init a region
MKCoordinateRegion region = MKCoordinateRegionMake(CLLocationCoordinate2DMake(51.0, 10.0), MKCoordinateSpanMake(2.0, 2.0));
[self.mapView setRegion:region animated:NO];
//add a dumy pin
DummyAnnotation *ann = [[DummyAnnotation alloc] init];
[self.mapView addAnnotation:ann];
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
if([annotation isKindOfClass:[DummyAnnotation class]]) {
DummyAnnotationView *view = (id)[mapView dequeueReusableAnnotationViewWithIdentifier:#"animated"];
if(!view)
view =[[DummyAnnotationView alloc ] initWithAnnotation:annotation reuseIdentifier:#"animated"];
view.bounds = CGRectMake(0, 0, 59, 59);
view.backgroundColor = [UIColor purpleColor];
//
//Animate it like any UIView!
//
CABasicAnimation *theAnimation;
//within the animation we will adjust the "opacity"
//value of the layer
theAnimation=[CABasicAnimation animationWithKeyPath:#"opacity"];
//animation lasts 0.4 seconds
theAnimation.duration=0.4;
//and it repeats forever
theAnimation.repeatCount= HUGE_VALF;
//we want a reverse animation
theAnimation.autoreverses=YES;
//justify the opacity as you like (1=fully visible, 0=unvisible)
theAnimation.fromValue=[NSNumber numberWithFloat:1.0];
theAnimation.toValue=[NSNumber numberWithFloat:0.1];
//Assign the animation to your UIImage layer and the
//animation will start immediately
[view.layer addAnimation:theAnimation forKey:#"animateOpacity"];
return view;
}
return nil;
}
//---
#end
uploaded the working sample to my dropbox (it will go away eventually but the code above is all but the image resources and the default boilerplate code of an iOS app) :: https://dl.dropbox.com/u/3753090/test.zip

Related

How to make the callout button in an annotation in MKMapKit go to another view controller

I want the button in the callout to go to another view controller to display extra information, and then be able to come back to the map view. the map is inside of a tabbed view controller. At the moment, if you click on the button in the callout, the app crashes and gives you a thread error. Not sure on what to do at the moment.
this is the header file:
#import UIKit/UIKit.h>
#import MapKit/MapKit.h>
#define METERS_PER_MILE 1609.344
#interface MSKSecondViewController :UIViewController<MKMapViewDelegate>
{
IBOutlet MKMapView *stillwellMapView;
}
#property (nonatomic, assign) CLLocationCoordinate2D mainEntranceToStillwellCoordinate;
#end
and this is the implementation file:
#interface MSKSecondViewController ()
#end
#implementation MSKSecondViewController
#synthesize mainEntranceToStillwellCoordinate;
- (void)viewDidLoad
{
[super viewDidLoad];
stillwellMapView.delegate=self;
// Do any additional setup after loading the view, typically from a nib.
[self mainEntrancetoStillwellCoordinate];
[self bambooForestCoordinate];
}
- (void)mainEntrancetoStillwellCoordinate
{
MKPointAnnotation * main = [[MKPointAnnotation alloc]init];
CLLocationCoordinate2D mainLocation;
mainLocation.latitude = 40.831685;
mainLocation.longitude = -73.477453;
[main setCoordinate:mainLocation];
[main setTitle:#"Entrance"];
[main setSubtitle:#"Main"];
[stillwellMapView addAnnotation:main];
}
- (void)bambooForestCoordinate
{
MKPointAnnotation * bambooForest = [[MKPointAnnotation alloc]init];
CLLocationCoordinate2D bambooForestLocation;
bambooForestLocation.latitude = 40.829118;
bambooForestLocation.longitude = -73.466443;
[bambooForest setCoordinate:bambooForestLocation];
[bambooForest setTitle:#"Bamboo Forest"];
[bambooForest setSubtitle:#"Exit to Woodbury"];
[stillwellMapView addAnnotation:bambooForest];
}
- (void)viewDidUnload
{
stillwellMapView = nil;
[super viewDidUnload];
// Release any retained subviews of the main view.
}
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:
(id<MKAnnotation>)annotation
{
MKPinAnnotationView * annView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:nil];
UIButton *entranceButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[entranceButton addTarget:self action:#selector(entranceButtonPressed:)
forControlEvents:UIControlEventTouchUpInside];
annView.rightCalloutAccessoryView = entranceButton;
entranceButton = [UIButton buttonWithType:UIButtonTypeCustom];
entranceButton.frame = CGRectMake(0, 0, 23, 23);
entranceButton.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
entranceButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
[UIButton buttonWithType:UIButtonTypeDetailDisclosure];
annView.animatesDrop=TRUE;
annView.canShowCallout = YES;
annView.calloutOffset = CGPointMake(-5, 5);
return annView;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
- (void)viewWillAppear:(BOOL)animated
{
//the coordinates in which the map shows once loaded
CLLocationCoordinate2D zoomLocation;
zoomLocation.latitude = 40.831922;
zoomLocation.longitude= -73.476353;
//the amount of area shown by the map when it loads
MKCoordinateRegion viewRegion = MKCoordinateRegionMakeWithDistance(zoomLocation,
0.15*METERS_PER_MILE, 0.15*METERS_PER_MILE);
MKCoordinateRegion adjustedRegion = [stillwellMapView regionThatFits:viewRegion];
[stillwellMapView setRegion:adjustedRegion animated:YES];
}
#end
Add the event which you have registered on Touch Up Inside with entranceButton.
-(void) entranceButtonPressed:(UIButton *)sender
{
//Write the controller push code here
}
Also, in the code you have reinitialize the entranceButton once it is assigned to rightCalloutAccessoryView.
In the mapView:viewForAnnotation: delegate method
-(MKAnnotationView *)mapView:(MKMapView *)mapView
viewForAnnotation:(id <MKAnnotation>)annotation
// add the following line
UIButton *rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
pinView.rightCalloutAccessoryView = rightButton;
and then implement the code to navigate to other view controller in the following delegate method
-(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
Make sure view controller is embedded inside navigation controller

Show another View Controller from an annotation callout button in MKMapKit

I am making an app that will use the MKMapKit for the pins and annotations. I want to display more information besides some text, so I created a button in the callout so I could make that go to another view controller. At the moment, the button in the callout doesn't do anything, as there is no code to make it work. It would be great if someone could make the code for the button to go to a different view controller!
The header file
#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
#import <CoreLocation/CoreLocation.h>
#define METERS_PER_MILE 1609.344
#interface MSKSecondViewController :UIViewController<MKMapViewDelegate>
{
IBOutlet MKMapView *stillwellMapView;
int difficultyOfTrailOverlay;
}
#property (nonatomic, assign) CLLocationCoordinate2D mainEntranceToStillwellCoordinate;
#end
Here is the implementation file
#import "MSKSecondViewController.h"
#interface MSKSecondViewController ()
#end
#implementation MSKSecondViewController
#synthesize mainEntranceToStillwellCoordinate;
- (void)viewDidLoad
{
[super viewDidLoad];
stillwellMapView.delegate=self;
// Do any additional setup after loading the view, typically from a nib.
[self mainEntrancetoStillwellCoordinate];
[self bambooForestCoordinate];
}
- (void)mainEntrancetoStillwellCoordinate
{
MKPointAnnotation * main = [[MKPointAnnotation alloc]init];
CLLocationCoordinate2D mainLocation;
mainLocation.latitude = 40.831685;
mainLocation.longitude = -73.477453;
[main setCoordinate:mainLocation];
[main setTitle:#"Entrance"];
[main setSubtitle:#"Main"];
[stillwellMapView addAnnotation:main];
}
- (void)bambooForestCoordinate
{
MKPointAnnotation * bambooForest = [[MKPointAnnotation alloc]init];
CLLocationCoordinate2D bambooForestLocation;
bambooForestLocation.latitude = 40.829118;
bambooForestLocation.longitude = -73.466443;
[bambooForest setCoordinate:bambooForestLocation];
[bambooForest setTitle:#"Bamboo Forest"];
[bambooForest setSubtitle:#"Exit to Woodbury"];
[stillwellMapView addAnnotation:bambooForest];
}
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation
(id<MKAnnotation>)annotation
{
MKPinAnnotationView * annView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation
reuseIdentifier:nil];
UIButton *moreInformationButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[moreInformationButton addTarget:self action:#selector(moreInformationButtonPressed:)
forControlEvents:UIControlEventTouchUpInside];
annView.rightCalloutAccessoryView = moreInformationButton;
moreInformationButton.frame = CGRectMake(0, 0, 23, 23);
moreInformationButton.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
moreInformationButton.contentHorizontalAlignment =
UIControlContentHorizontalAlignmentCenter;
annView.pinColor =MKPinAnnotationColorGreen;
annView.animatesDrop = true;
annView.canShowCallout = TRUE;
return annView;
}
I'm not sure if this is what I need to add, and what needs to go inside here
-(void) moreInformationButtonPressed:(UIButton *)sender
{
}
Write below kind of code:
-(void) moreInformationButtonPressed:(UIButton *)sender
{
OtherViewController *controller = [[OtherViewController alloc]initWithNibName:#"OtherViewController" bundle:nil];
[self.navigationController pushViewController:controller animated:YES];
[controller release];
}
first you should make a tag that you can know which annotation is clicked
moreInformationButton.tag = someIndex;
then do the action you want
-(void) moreInformationButtonPressed:(UIButton *)sender
{
int index = sender.tag;
YourController *yc =[[[YourController alloc] initWithIndex:index] autorelease];
[self.navigationController pushViewController:scanView animated:YES];
}

How to render UITableViewCell

I create UITabBar template. Then I add to firstViewController a tableView. Make connections (UITableViewDataSource and UITableViewDelegate).
In didselect method I need to render selected UITableViewCell and animate it to one of UITabBars.
If I render UITableViewCell row = 0 - it's ok, But if I render UITableViewCell row > 0 (1,2,...) I get black rectangle.
I can render UITableViewCell contentView, but it will render without background.
How can I get snapshot of UITableViewCell with a background?
#import <UIKit/UIKit.h>
#interface AppDelegate : UIResponder <UIApplicationDelegate, UITabBarControllerDelegate>
#property (strong, nonatomic) UIWindow *window;
#property (strong, nonatomic) UITabBarController *tabBarController;
#end
#import "AppDelegate.h"
#import "FirstViewController.h"
#import "SecondViewController.h"
#implementation AppDelegate
#synthesize window = _window;
#synthesize tabBarController = _tabBarController;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions: (NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
UIViewController *viewController1 = [[FirstViewController alloc] initWithNibName:#"FirstViewController" bundle:nil];
UIViewController *viewController2 = [[SecondViewController alloc] initWithNibName:#"SecondViewController" bundle:nil];
self.tabBarController = [[UITabBarController alloc] init];
self.tabBarController.viewControllers = [NSArray arrayWithObjects:viewController1, viewController2, nil];
self.window.rootViewController = self.tabBarController;
[self.window makeKeyAndVisible];
return YES;
}
#import <UIKit/UIKit.h>
#interface FirstViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
#property (nonatomic, strong) IBOutlet UITableView *tableViewAnimation;
#property (nonatomic, retain) UIImageView *thumbnail;
- (UIImage*)screenshotFromView:(UIView *)view;
#end
#import "FirstViewController.h"
#import <QuartzCore/QuartzCore.h>
#implementation FirstViewController
#synthesize tableViewAnimation = _tableViewAnimation;
#synthesize thumbnail = _thumbnail;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.title = NSLocalizedString(#"First", #"First");
self.tabBarItem.image = [UIImage imageNamed:#"first"];
}
return self;
}
#pragma mark - UITableView DataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSUInteger count = 10;
return count;
}
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *goodsListCellIdentifier = #"ListOfGoodsIndetifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:goodsListCellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:goodsListCellIdentifier];
cell.textLabel.backgroundColor = [UIColor clearColor];
}
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.textLabel.text = #"detalied view";
return cell;
}
#pragma mark - UITableView Delegate
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
cell.backgroundColor = [UIColor redColor];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [_tableViewAnimation cellForRowAtIndexPath:indexPath];
CGRect imageViewFrame = cell.frame;
imageViewFrame.origin.y = 64.0 + tableView.frame.origin.y + imageViewFrame.origin.y - tableView.contentOffset.y;
// imageViewFrame.size.width = cell.contentView.frame.size.width;
UIImageView *imageView = [[UIImageView alloc] initWithFrame:imageViewFrame];
imageView.image = [self screenshotFromView:cell];
[self.tabBarController.view addSubview:imageView];
self.thumbnail = imageView;
self.thumbnail.hidden = NO;
self.view.userInteractionEnabled = NO;
[UIView animateWithDuration:1.0
animations:^{
CGRect tabBarFrame = self.tabBarController.tabBar.frame;
CGFloat xoffset = tabBarFrame.size.width / 2;
[_thumbnail setCenter:CGPointMake(xoffset * 0.5, tabBarFrame.origin.y + 25.0)];
[_thumbnail setTransform:CGAffineTransformMakeScale(0.1, 0.1)];
[_thumbnail setAlpha:0.4];
}
completion:^(BOOL finished){
[self.thumbnail removeFromSuperview];
self.thumbnail = nil;
self.view.userInteractionEnabled = YES;
}];
}
- (UIImage*)screenshotFromView:(UIView *)view {
// Create a graphics context with the target size
// On iOS 4 and later, use UIGraphicsBeginImageContextWithOptions to take the scale into consideration
// On iOS prior to 4, fall back to use UIGraphicsBeginImageContext
CGSize imageSize = view.bounds.size;
UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
// -renderInContext: renders in the coordinate space of the layer,
// so we must first apply the layer's geometry to the graphics context
CGContextSaveGState(context);
// Center the context around the window's anchor point
CGContextTranslateCTM(context, [view center].x, [view center].y);
// Apply the window's transform about the anchor point
CGContextConcatCTM(context, [view transform]);
// Offset by the portion of the bounds left of and above the anchor point
CGContextTranslateCTM(context, -[view bounds].size.width * [[view layer] anchorPoint].x, -[view bounds].size.height * [[view layer] anchorPoint].y);
// Render the layer hierarchy to the current context
[[view layer] renderInContext:context];
// Restore the context
CGContextRestoreGState(context);
// Retrieve the screenshot image
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
#end
The background is probably not rendered automatically by the cell but by the table instead.
Note that background is rendered depending on cell state (background / selected background).
You can take cell.backgroundView and render it before you render the cell.
[((UITableViewCell*) view).backgroundView.layer renderInContext:context]
But that's only a guess.
Maybe it's something simpler. When you tap a cell, selection animation is started. renderInContext ignores animations. Maybe there is a problem?
Just comment next lines:
// Center the context around the window's anchor point
// CGContextTranslateCTM(context, [view center].x, [view center].y);
// Apply the window's transform about the anchor point
// CGContextConcatCTM(context, [view transform]);
// Offset by the portion of the bounds left of and above the anchor point
// CGContextTranslateCTM(context, -[view bounds].size.width * [[view layer] anchorPoint].x, -[view bounds].size.height * [[view layer] anchorPoint].y);

CALayer delegate callbacks in a UITableViewCell - Workflow

I'm using several CALayers to draw my content in a UITableViewCell subclass. The actual drawing is supposed to happen by calling the CALayer delegate method drawLayer:inContext:. However the delegate methods never gets called.
Here is my structure:
CustomCell: UITalbeViewCell
creates CustomCellContentView: UIView
creates CABackgroundLayer: CALayer
creates CATextLayer: CALayer
creates CALayerDelegate: NSObject
Each customCell has a customContentView, which holds all the CALayers for drawing.
Each customCell creates a caLayerDelegate for the CALayers to call. The caLayerDelegate reports back to the customCell.
CustomCell:
#define CALAYER_TEXTLAYER_NAME #"CATextLayer"
#define CALAYER_BGLAYER_NAME #"CABackgroundLayer"
...
- (id) customContentViewForCurrentCell
{
CGRect contentViewFrame = CGRectMake(-CC_LEFTRIGHT_PADDING, 0., self.contentView.bounds.size.width, self.contentView.bounds.size.height);
CustomCellContentView *cellContentView = [[[CustomCellContentView alloc] initWithFrame: contentViewFrame] autorelease];
CALayerDelegate *layerDelegate = [[CALayerDelegate alloc] initWithView: self];
cellContentView.layerDelegate = layerDelegate;
[cellContentView setupSubLayers];
[layerDelegate release];
return cellContentView;
}
#pragma mark - Initialisation
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (!self)
return nil;
self.customContentView = (CustomCellContentView *)[self customContentViewForCurrentCell];
self.customContentView.opaque = TRUE;
self.backgroundColor = [UIColor clearColor];
self.selectionStyle = UITableViewCellSelectionStyleNone;
[self.contentView addSubview: self.customContentView];
return self;
}
#pragma mark - Cell Update
- (void) refreshLayout
{
[self setNeedsDisplay];
[self.customContentView setNeedsDisplay];
}
...
#pragma mark - CALayer Delegate Methods
-(void) drawLayer: (CALayer*) layer inContext: (CGContextRef) context
{
}
-(void) drawCABackgroundLayer: (CALayer*) layer inContext: (CGContextRef) context
{
UIGraphicsPushContext(context);
CGRect contentRect = [layer bounds];
UIImage *bgImage = [[ImageCacheController sharedImageCache] imageFromCache: GENERIC_BGIMAGE_FILENAME];
[bgImage drawInRect: CGRectMake(contentRect.origin.x, contentRect.origin.y, contentRect.size.width, contentRect.size.height)];
UIGraphicsPopContext();
}
-(void) drawCATextLayer: (CALayer*) layer inContext: (CGContextRef) context
{
UIGraphicsPushContext(context);
CGContextSetShadowWithColor(context, CGSizeMake(0.f, 2.0f), 0.0f, [UIColor mainLabelShadowColor].CGColor);
[[UIColor mainLabelColor] set];
UIFont *mainFont = [UIFont fontWithName: FONT_INFOS size: CC_MAINLABEL_FONTSIZE_MAX];
[#"ArthurDent" drawAtPoint: [self mainViewPosition]
forWidth: CC_MAINLABEL_WIDTH
withFont: mainFont
minFontSize: CC_MAINLABEL_FONTSIZE_MAX
actualFontSize: NULL
lineBreakMode: UILineBreakModeTailTruncation
baselineAdjustment: UIBaselineAdjustmentAlignBaselines];
UIGraphicsPopContext();
}
CustomCellContentView:
#interface CustomCellContentView : UIView
{
CALayerDelegate *layerDelegate;
CALayer *backgroundLayer;
CALayer *textLayer;
}
#property (nonatomic, retain) CALayerDelegate *layerDelegate;
#property (nonatomic, retain) CALayer *backgroundLayer;
#property (nonatomic, retain) CALayer *textLayer;
- (void) setupSubLayers;
#end
#implementation CustomCellContentView
#synthesize textLayer, backgroundLayer, layerDelegate;
#pragma mark - Initialisation
- (id) initWithFrame:(CGRect)frame
{
self = [super initWithFrame: frame];
if(!self)
return nil;
return self;
}
- (void) setupSubLayers
{
self.textLayer = [CALayer layer];
self.textLayer.name = CALAYER_TEXTLAYER_NAME;
self.textLayer.shadowColor = [UIColor mainLabelShadowColor].CGColor;
self.textLayer.shadowOffset = CGSizeMake(0.0f, 2.0f);
self.textLayer.shadowOpacity = 1.f;
self.textLayer.shadowRadius = 0.f;
self.textLayer.needsDisplayOnBoundsChange = TRUE;
self.textLayer.delegate = self.layerDelegate;
self.backgroundLayer = [CALayer layer];
self.backgroundLayer.name = CALAYER_BGLAYER_NAME;
self.backgroundLayer.needsDisplayOnBoundsChange = TRUE;
self.backgroundLayer.delegate = self.layerDelegate;
[self.layer addSublayer: self.textLayer];
[self.layer addSublayer: self.backgroundLayer];
[self.textLayer setNeedsDisplay];
[self.backgroundLayer setNeedsDisplay];
}
#pragma mark - SetNeedsDisplay Overwritten
- (void) setNeedsDisplay
{
[super setNeedsDisplay];
[self.layer setNeedsDisplay];
[self.textLayer setNeedsDisplay];
[self.backgroundLayer setNeedsDisplay];
}
...
CALayerDelegate:
#implementation CALayerDelegate
#pragma mark - Initialisation
-(id) initWithView: (UIView *) view
{
self = [super init];
if (!self)
return nil;
_view = view;
return self;
}
#pragma mark - CALayer Delegate Methods
-(void) drawLayer: (CALayer*) layer inContext: (CGContextRef) context
{
NSString* methodName = [NSString stringWithFormat: #"draw%#:inContext:", [layer name]];
SEL selector = NSSelectorFromString(methodName);
if (![_view respondsToSelector: selector])
selector = #selector(drawLayer:inContext:);
[_view performSelector: selector
withObject: layer
withObject: (id)context];
}
#end
I've read for UIViewController it is better to create sub-CALayers in awakeFromNib: and not in initWithFrame:, because there is some form of layer - backing happening after the initWithFrame:, which will discard the creation of the sublayers. So, you should only add sublayers after the view is loaded?
I use seperate CALayerDelegate - objects, so the CustomCell and its view are only responsible for the customContentView.layer.
Anyone knows, what I'm doing wrong?
Thanks in advance!!
From the documentation for CALayer describing the 'delegate' property: 'In iOS, if the layer is associated with a UIView object, this property must be set to the view that owns the layer'.
If I read that correctly then you need to set CustomCellContentView as the layer delegate. Move the functionality from CALayerDelegate directly into your custom view class and see if that works. That would be the standard way to do it anyway, rather than creating a separate class just to act as a delegate.
Check that your frame is not CGRectZero.
I had a similar issue to what you describe. If the frame is CGRectZero this will prevent the drawLayer:inContext from being called.

Extend TTPhotoViewController with custom TTPhotoView

I have successfully integrated the three20 framework in my project,
and I have extended the TTPhotoViewController to add some further
functionality.
Now I need to add some subviews to the TTPhotoView loaded by the
TTPhotoViewController. In particular I would like to add that subviews
after every TTPhotoView as been loaded. These subviews represents
sensible area over the image so they should scale proportionally with
the image.
The user can tap a subview to get extra info about the image.
I don't know how to implement this behavior. Should I extend the
TTPhotoView and make sure that the TTPhotoViewController use this
extended version instead of its TTPhotoView?
Could someone point me to the right direction?
Thank you
Solved subclassing the TTPhotoView (TapDetectingPhotoView) and then adding all my subviews to that subclass.
The main problem was represented by the photo switching, because the TTPhotoViewController (in particular its inner TTScrollView) reuse the TTPhotoView during switching operation.
So for example if you add your subview to your TTPhotoView subclass and try to switch to the next photo, your subview will probably be here, because the TTPhotoView is reused.
To solve this problem I decided to add and remove all my subviews every time a photo switch occur (see TTPhotoViewController::didMoveToPhoto).
In this way I'm sure that every photoview has its subviews.
Here my implementation (only remarkable methods)
Hope these help!
PhotoViewController.h:
#import "TapDetectingPhotoView.h"
#interface PhotoGalleryController : TTPhotoViewController <TTScrollViewDelegate, TapDetectingPhotoViewDelegate> {
NSArray *images;
}
#property (nonatomic, retain) NSArray *images;
#end
PhotoViewController.m:
#import "PhotoGalleryController.h"
#implementation PhotoGalleryController
#synthesize images;
- (void)viewDidLoad { // fill self.images = ... }
- (TTPhotoView*)createPhotoView {
TapDetectingPhotoView *photoView = [[TapDetectingPhotoView alloc] init];
photoView.tappableAreaDelegate = self;
return [photoView autorelease];
}
#pragma mark -
#pragma mark TTPhotoViewController
- (void)didMoveToPhoto:(id<TTPhoto>)photo fromPhoto:(id<TTPhoto>)fromPhoto {
[super didMoveToPhoto:photo fromPhoto:fromPhoto];
TapDetectingPhotoView *previousPhotoView = (TapDetectingPhotoView *)[_scrollView pageAtIndex:fromPhoto.index];
TapDetectingPhotoView *currentPhotoView = (TapDetectingPhotoView *)[_scrollView pageAtIndex:photo.index];
// destroy the sensible areas from the previous photoview, because the photo could be reused by the TTPhotoViewController!
if (previousPhotoView)
previousPhotoView.sensibleAreas = nil;
// if sensible areas has not been already created, create new
if (currentPhotoView && currentPhotoView.sensibleAreas == nil) {
currentPhotoView.sensibleAreas = [[self.images objectAtIndex:photo.index] valueForKey:#"aMap"];
[self showSensibleAreas:YES animated:YES];
}
}
#pragma mark -
#pragma mark TappablePhotoViewDelegate
// show a detail view when a sensible area is tapped
- (void)tapDidOccurOnSensibleAreaWithId:(NSUInteger)ids {
NSLog(#"SENSIBLE AREA TAPPED ids:%d", ids);
// ..push new view controller...
}
TapDetectingPhotoView.h:
#import "SensibleAreaView.h"
#protocol TapDetectingPhotoViewDelegate;
#interface TapDetectingPhotoView : TTPhotoView <SensibleAreaViewDelegate> {
NSArray *sensibleAreas;
id <TapDetectingPhotoViewDelegate> tappableAreaDelegate;
}
#property (nonatomic, retain) NSArray *sensibleAreas;
#property (nonatomic, assign) id <TapDetectingPhotoViewDelegate> tappableAreaDelegate;
#end
#protocol TapDetectingPhotoViewDelegate <NSObject>
#required
- (void)tapDidOccurOnSensibleAreaWithId:(NSUInteger)ids;
#end
TapDetectingPhotoView.m:
#import "TapDetectingPhotoView.h"
#interface TapDetectingPhotoView (Private)
- (void)createSensibleAreas;
#end
#implementation TapDetectingPhotoView
#synthesize sensibleAreas, tappableAreaDelegate;
- (id)init {
return [self initWithSensibleAreas:nil];
}
- (id)initWithFrame:(CGRect)frame {
return [self initWithSensibleAreas:nil];
}
// designated initializer
- (id)initWithSensibleAreas:(NSArray *)areasList {
if (self = [super initWithFrame:CGRectZero]) {
self.sensibleAreas = areasList;
[self createSensibleAreas];
}
return self;
}
- (void)setSensibleAreas:(NSArray *)newSensibleAreas {
if (newSensibleAreas != self.sensibleAreas) {
// destroy previous sensible area and ensure that only sensible area's subviews are removed
for (UIView *subview in self.subviews)
if ([subview isMemberOfClass:[SensibleAreaView class]])
[subview removeFromSuperview];
[newSensibleAreas retain];
[sensibleAreas release];
sensibleAreas = newSensibleAreas;
[self createSensibleAreas];
}
}
- (void)createSensibleAreas {
SensibleAreaView *area;
NSNumber *areaID;
for (NSDictionary *sensibleArea in self.sensibleAreas) {
CGFloat x1 = [[sensibleArea objectForKey:#"nX1"] floatValue];
CGFloat y1 = [[sensibleArea objectForKey:#"nY1"] floatValue];
area = [[SensibleAreaView alloc] initWithFrame:
CGRectMake(
x1, y1,
[[sensibleArea objectForKey:#"nX2"] floatValue]-x1, [[sensibleArea objectForKey:#"nY2"] floatValue]-y1
)
];
areaID = [sensibleArea objectForKey:#"sId"];
area.ids = [areaID unsignedIntegerValue]; // sensible area internal ID
area.tag = [areaID integerValue];
area.delegate = self;
[self addSubview:area];
[area release];
}
}
// to make sure that if the zoom factor of the TTScrollView is > than 1.0 the subviews continue to respond to the tap events
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *result = nil;
for (UIView *child in self.subviews) {
CGPoint convertedPoint = [self convertPoint:point toView:child];
if ([child pointInside:convertedPoint withEvent:event]) {
result = child;
}
}
return result;
}
#pragma mark -
#pragma mark TapDetectingPhotoViewDelegate methods
- (void)tapDidOccur:(SensibleAreaView *)aView {
NSLog(#"tapDidOccur ids:%d tag:%d", aView.ids, aView.tag);
[tappableAreaDelegate tapDidOccurOnSensibleAreaWithId:aView.ids];
}
SensibleAreaView.h:
#protocol SensibleAreaViewDelegate;
#interface SensibleAreaView : UIView {
id <SensibleAreaViewDelegate> delegate;
NSUInteger ids;
UIButton *disclosureButton;
}
#property (nonatomic, assign) id <SensibleAreaViewDelegate> delegate;
#property (nonatomic, assign) NSUInteger ids;
#property (nonatomic, retain) UIButton *disclosureButton;
#end
#protocol SensibleAreaViewDelegate <NSObject>
#required
- (void)tapDidOccur:(SensibleAreaView *)aView;
#end
SensibleAreaView.m:
#import "SensibleAreaView.h"
#implementation SensibleAreaView
#synthesize delegate, ids, disclosureButton;
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.userInteractionEnabled = YES;
UIColor *color = [[UIColor alloc] initWithWhite:0.4 alpha:1.0];
self.backgroundColor = color;
[color release];
UIButton *button = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[button addTarget:self action:#selector(buttonTouched) forControlEvents:UIControlEventTouchUpInside];
CGRect buttonFrame = button.frame;
// set the button position over the right edge of the sensible area
buttonFrame.origin.x = frame.size.width - buttonFrame.size.width + 5.0f;
buttonFrame.origin.y = frame.size.height/2 - 10.0f;
button.frame = buttonFrame;
button.autoresizingMask = UIViewAutoresizingFlexibleTopMargin |UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleLeftMargin |UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleWidth |UIViewAutoresizingFlexibleHeight;
self.disclosureButton = button;
[self addSubview:button];
// notification used to make sure that the button is properly scaled together with the photoview. I do not want the button looks bigger if the photoview is zoomed, I want to preserve its default dimensions
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(zoomFactorChanged:) name:#"zoomFactorChanged" object:nil];
}
return self;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
if ([[touches anyObject] tapCount] == 1)
[delegate tapDidOccur:self];
}
- (void)buttonTouched {
[delegate tapDidOccur:self];
}
- (void)zoomFactorChanged:(NSNotification *)message {
NSDictionary *userInfo = [message userInfo];
CGFloat factor = [[userInfo valueForKey:#"zoomFactor"] floatValue];
BOOL withAnimation = [[userInfo valueForKey:#"useAnimation"] boolValue];
if (withAnimation) {
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.18];
}
disclosureButton.transform = CGAffineTransformMake(1/factor, 0.0, 0.0, 1/factor, 0.0, 0.0);
if (withAnimation)
[UIView commitAnimations];
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"zoomFactorChanged" object:nil];
[disclosureButton release];
[super dealloc];
}
Some ideas:
Subclass TTPhotoView, then override createPhotoView in your TTPhotoViewController:
- (TTPhotoView*)createPhotoView {
return [[[MyPhotoView alloc] init] autorelease];
}
Try overriding a private method (yes, bad practice, but hey) setImage: in TTPhotoView subclass:
- (void)setImage:(UIImage*)image {
[super setImage:image]
// Add a subview with the frame of self.view, maybe?..
//
// Check for self.isLoaded (property of TTImageView
// which is subclassed by TTPhotoView) to check if
// the image is loaded
}
In general, look at the headers and implementations (for the private methods) of TTPhotoViewController and TTPhotoView. Set some breakpoints to figure out what does what :)
Interesting question. Facebook has a similar functionality with their tags. Their tags do not scale proportionally with the image. In fact, they do not even show the tags if you have zoomed. I don't know if this will help you, but I would look at how (if) three20 handles tags and then maybe try to extend that.