Alright, having an odd problem, my polyline that is set as an overlay for my map shows up fine unless I try to access an enumerated variable in the rendererForOverlay method.
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay
{
if ([overlay isKindOfClass:[MKGeodesicPolyline class]]) //If I change MKGeodesicPolyline to my subclass it won't render the overlay.
{
CustomLine *poly = (CustomLine *)overlay;
MKPolylineRenderer *renderer = [[MKPolylineRenderer alloc] initWithPolyline:(CustomLine *)overlay];
//[poly testFunction]; //This will also cause overlay to not show
//If I uncomment this it will cause the overlay to not show
/*
if (*poly.linecheck == Clear)
{
NSLog(#"Overlay should be red");
renderer.strokeColor = [NSColor greenColor];
}
if (*poly.linecheck == Bad)
{
NSLog(#"Overlay should be green");
renderer.strokeColor = [NSColor redColor];
}
*/
renderer.strokeColor = [NSColor redColor];
//NSLog(#"polyline.linecheck %u",*poly.linecheck); //if this is uncommented it will also break the overlay.
renderer.lineWidth = 3.0;
return renderer;
}
return nil;
Here is my custom polyline's header.
#import <MapKit/MapKit.h>
enum linecheck
{
Clear,
Bad
};
#interface CustomLine : MKGeodesicPolyline
{
}
#property (readwrite) enum linecheck *linecheck;
Related
In my collection view when (custom) cells are reused they, again, get the highlight I have set in didSelectItemAtIndexPath for the original selection. To prevent this, I am using the custom cell's prepareForReuse method, and post calling [super], I check to see if its the selected cell.
If it is I am change the highlight to default else I restore to the original selection highlight when the cell in question is brought back in scroll view's visible area.
Here's the code...
- (void)prepareForReuse
{
[super prepareForReuse];
if (!self.isSelected) {
[self setBackgroundColor:[UIColor systemBackgroundColor]];
[_tagImageView setTintColor:[UIColor systemBlueColor]];
}
else if (self.isSelected)
{
[self setBackgroundColor:[UIColor systemBlueColor]];
[_tagImageView setTintColor:[UIColor systemBackgroundColor]];
}
}
But I notice that the second if block is never executed even when I bring back the original cell in view. This is where I need help. How do I ensure re-highlighting or the original cell/item?
Note, if I try and save the original cell- even though not highlighted, remains the one selected and the corresponding value is saved.
So, this is just about the re-highlight.
Also, here is the selection code...* didSelectItemAtIndexPath*
- (void)collectionView:(UICollectionView *)collectionView
didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
if (selectedIndexPath!=nil) {
if (indexPath.row==selectedIndexPath.row)
{
[tagCollectionView deselectItemAtIndexPath:indexPath animated:YES];
TagCollectionViewCell *selectedCell = (TagCollectionViewCell *)[tagCollectionView cellForItemAtIndexPath:selectedIndexPath];
selectedCell.backgroundColor = [UIColor clearColor];
selectedCell.tagImageView.tintColor = [UIColor systemBlueColor];
selectedIndexPath=nil;
[newDictionary setValue:[NSNull null] forKey:#"type"];
}
else
{
[tagCollectionView deselectItemAtIndexPath:indexPath animated:YES];
TagCollectionViewCell *previousSelectedCell = (TagCollectionViewCell *)[tagCollectionView cellForItemAtIndexPath:selectedIndexPath];
previousSelectedCell.backgroundColor = [UIColor systemBackgroundColor];
previousSelectedCell.tagImageView.tintColor = [UIColor systemBlueColor];
selectedIndexPath = indexPath;
TagCollectionViewCell *selectedCell = (TagCollectionViewCell *)[tagCollectionView cellForItemAtIndexPath:selectedIndexPath];
selectedCell.backgroundColor = [UIColor systemBlueColor];
selectedCell.tagImageView.tintColor = [UIColor systemBackgroundColor];
dictionaryType = _typesArray[selectedIndexPath.row];
[newDictionary setValue:dictionaryType forKey:#"type"];
}
}
else if (selectedIndexPath==nil)
{
selectedIndexPath = indexPath;
TagCollectionViewCell *selectedCell = (TagCollectionViewCell *)[tagCollectionView cellForItemAtIndexPath:selectedIndexPath];
selectedCell.backgroundColor = [UIColor systemBlueColor];
selectedCell.tagImageView.tintColor = [UIColor systemBackgroundColor];
dictionaryType = _typesArray[selectedIndexPath.row];
[newDictionary setValue:dictionaryType forKey:#"type"];
}
}
Any help? Thanks.
Edit:
This is the part of the code that doesn't get called.
else if (self.isSelected)
{
[self setBackgroundColor:[UIColor systemBlueColor]];
[_tagImageView setTintColor:[UIColor systemBackgroundColor]];
}
I think you are way over-complicating things.
A UICollectionView keeps track of its own "selected" cell(s), and calls setSelected on each cell when it is displayed.
You can put all of your "selected" appearance code inside your cell class:
- (void)setSelected:(BOOL)selected {
// change our color properties based on selected BOOL value
self.tagImageView.tintColor = selected ? UIColor.systemBackgroundColor : UIColor.systemBlueColor;
self.backgroundColor = selected ? UIColor.systemBlueColor : UIColor.systemBackgroundColor;
}
Now you don't need to do anything in didSelectItemAt.
Here's a quick example...
SampleCollectionViewCell.h
#interface SampleCollectionViewCell : UICollectionViewCell
- (void)fillData:(NSInteger)n;
#end
SampleCollectionViewCell.m
#import "SampleCollectionViewCell.h"
#interface SampleCollectionViewCell ()
{
UIImageView *theImageView;
UILabel *theLabel;
}
#end
#implementation SampleCollectionViewCell
- (instancetype)init
{
self = [super init];
if (self) {
[self commonInit];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self commonInit];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
[self commonInit];
}
return self;
}
- (void)commonInit {
// add an image view and a label
theImageView = [UIImageView new];
theImageView.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentView addSubview:theImageView];
theLabel = [UILabel new];
theLabel.textAlignment = NSTextAlignmentCenter;
theLabel.font = [UIFont systemFontOfSize:20.0 weight:UIFontWeightBold];
theLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentView addSubview:theLabel];
[NSLayoutConstraint activateConstraints:#[
[theImageView.topAnchor constraintEqualToAnchor:self.contentView.topAnchor constant:0.0],
[theImageView.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor constant:0.0],
[theImageView.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor constant:0.0],
[theImageView.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor constant:0.0],
[theLabel.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor constant:0.0],
[theLabel.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor constant:0.0],
[theLabel.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor constant:-4.0],
]];
// image would probably be set by the data source, but
// for this example we'll use the same system image in every cell
UIImage *img = [UIImage systemImageNamed:#"person.fill"];
if (img) {
theImageView.image = img;
}
// let's give the content view rounded corners and a border
self.contentView.layer.cornerRadius = 8.0;
self.contentView.layer.borderWidth = 2.0;
self.contentView.layer.borderColor = UIColor.systemGreenColor.CGColor;
// default (not-selected) colors
theImageView.tintColor = UIColor.cyanColor;
theLabel.textColor = UIColor.blackColor;
self.contentView.backgroundColor = UIColor.systemBackgroundColor;
}
- (void)fillData:(NSInteger)n {
theLabel.text = [NSString stringWithFormat:#"%ld", (long)n];
}
- (void)setSelected:(BOOL)selected {
// change our color properties based on selected BOOL value
theImageView.tintColor = selected ? UIColor.redColor : UIColor.cyanColor;
theLabel.textColor = selected ? UIColor.yellowColor : UIColor.blackColor;
self.contentView.backgroundColor = selected ? UIColor.systemBlueColor : UIColor.systemBackgroundColor;
}
#end
SampleViewController.h
#interface SampleViewController : UIViewController <UICollectionViewDelegate, UICollectionViewDataSource>
#end
SampleViewController.m
#import "SampleViewController.h"
#import "SampleCollectionViewCell.h"
#interface SampleViewController ()
{
UICollectionView *collectionView;
}
#end
#implementation SampleViewController
- (void)viewDidLoad {
[super viewDidLoad];
UICollectionViewFlowLayout *fl = [UICollectionViewFlowLayout new];
fl.scrollDirection = UICollectionViewScrollDirectionVertical;
fl.itemSize = CGSizeMake(60, 60);
fl.minimumLineSpacing = 8;
fl.minimumInteritemSpacing = 8;
collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:fl];
collectionView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:collectionView];
UILayoutGuide *g = [self.view safeAreaLayoutGuide];
[NSLayoutConstraint activateConstraints:#[
// constrain collection view 40-points from all 4 sides
[collectionView.topAnchor constraintEqualToAnchor:g.topAnchor constant:40.0],
[collectionView.leadingAnchor constraintEqualToAnchor:g.leadingAnchor constant:40.0],
[collectionView.trailingAnchor constraintEqualToAnchor:g.trailingAnchor constant:-40.0],
[collectionView.bottomAnchor constraintEqualToAnchor:g.bottomAnchor constant:-40.0],
]];
[collectionView registerClass:SampleCollectionViewCell.class forCellWithReuseIdentifier:#"c"];
collectionView.dataSource = self;
collectionView.delegate = self;
// let's give the collection view a very light gray background
// so we can see its frame
collectionView.backgroundColor = [UIColor colorWithWhite:0.95 alpha:1.0];
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return 50;
}
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
SampleCollectionViewCell *c = (SampleCollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:#"c" forIndexPath:indexPath];
[c fillData:indexPath.item];
return c;
}
#end
Based on the code you posted, it looks like you want to be able to de-select an already selected cell. If so, add this to the controller:
// this allows us to de-select an already selected cell
- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath {
// get array of already selected index paths
NSArray *a = [collectionView indexPathsForSelectedItems];
// if that array contains indexPath, that means
// it is already selected, so
if ([a containsObject:indexPath]) {
// deselect it
[collectionView deselectItemAtIndexPath:indexPath animated:NO];
return NO;
}
// no indexPaths (cells) were selected
return YES;
}
When run, it starts like this:
Tapping cell "1" selects it:
Tapping cell "7" automatically de-selects cell "1" and selects cell "7":
We can scroll up and down and the selected cell will automatically maintain its "selected appearance":
Edit
To explain why your prepareForReuse wasn't doing what you expected...
The collection view does not set the selected property of the cell until it is going to be displayed.
So, in:
- (void)prepareForReuse
{
[super prepareForReuse];
if (!self.isSelected) {
[self setBackgroundColor:[UIColor systemBackgroundColor]];
[_tagImageView setTintColor:[UIColor systemBlueColor]];
}
else if (self.isSelected)
{
[self setBackgroundColor:[UIColor systemBlueColor]];
[_tagImageView setTintColor:[UIColor systemBackgroundColor]];
}
}
self.isSelected will never be true.
If you want to stick with changing the cell UI properties (colors, tint, etc) in didSelectItemAt, you need to update your cell appearance in cellForItemAt:
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
TagCollectionViewCell *c = (TagCollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:#"c" forIndexPath:indexPath];
// whatever you are currently doing, such as
//c.tagImageView.image = ...;
if (selectedIndexPath != indexPath) {
[c setBackgroundColor:[UIColor systemBackgroundColor]];
[c.tagImageView setTintColor:[UIColor systemBlueColor]];
}
else
{
[c setBackgroundColor:[UIColor systemBlueColor]];
[c.tagImageView setTintColor:[UIColor systemBackgroundColor]];
}
return c;
}
I want to draw the text in an UITextField with a shadow. In order to do this, I have subclassed UITextField, and implemented the drawTextInRect: method as follows:
- (void)drawTextInRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
// Create shadow color
float colorValues[] = {0.21875, 0.21875, 0.21875, 1.0};
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGColorRef shadowColor = CGColorCreate(colorSpace, colorValues);
CGColorSpaceRelease(colorSpace);
// Create shadow
CGSize shadowOffset = CGSizeMake(2, 2);
CGContextSetShadowWithColor(context, shadowOffset, 0, shadowColor);
CGColorRelease(shadowColor);
// Render text
[super drawTextInRect:rect];
}
This works great for when the text field is not editing, but as soon as editing begins, the shadow disappears. Is there anything I am missing?
Here is the code for following component
#interface AZTextField ()
- (void)privateInitialization;
#end
#implementation AZTextField
static CGFloat const kAZTextFieldCornerRadius = 3.0;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (!self) return nil;
[self privateInitialization];
return self;
}
// In case you decided to use it in a nib
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (!self) return nil;
[self privateInitialization];
return self;
}
- (void)privateInitialization
{
self.borderStyle = UITextBorderStyleNone;
self.layer.masksToBounds = NO;
self.layer.shadowColor = [UIColor blackColor].CGColor;
self.layer.shadowOffset = CGSizeMake(0.0f, 5.0f);
self.layer.shadowOpacity = 0.5f;
self.layer.backgroundColor = [UIColor whiteColor].CGColor;
self.layer.cornerRadius = 4;
// This code is better to be called whenever size of the textfield changed,
// so if you plan to do that you can add an observer for bounds property
UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:kAZTextFieldCornerRadius];
self.layer.shadowPath = shadowPath.CGPath;
}
#end
Couple of things to consider:
You want to set borderStyle to none, otherwise you'll end up with
UIKit putting subviews into your textfield
Depending on the Xcode
version you might want to link QuartzCore and to #import
<QuartzCore/QuartzCore.h>
For more complex appearance you can still
use shadow properties of the layer and move the drawing code itself
into the drawRect: method, but jake_hetfield was right if you
override drawRect you don't want to call super, especially in the end
of the method
As for the text drawing (you can see that it sticks to
close to the component borders), you have a separate
drawTextInRect: and drawPlaceholderInRect: method that draws the
text and placeholder respectively
You can use UIColor method for
colors and call CGColor property, it makes code more readable and
easier to maintain
Hope that helps!
Inspired by #jake_hetfield answer I created a custom UITextField that uses an internal label to do the drawing, check it out:
ShadowTextField .h file
#import <UIKit/UIKit.h>
#interface ShadowTextField : UITextField
// properties to change the shadow color & offset
#property (nonatomic, retain) UIColor *textShadowColor;
#property (nonatomic) CGSize textShadowOffset;
- (id)initWithFrame:(CGRect)frame
font:(UIFont *)font
textColor:(UIColor *)textColor
shadowColor:(UIColor *)shadowColor
shadowOffset:(CGSize)shadowOffset;
#end
ShadowTextField .m file
#import "ShadowTextField.h"
#interface ShadowTextField ()
#property (nonatomic, retain) UILabel *internalLabel;
#end
#implementation ShadowTextField
#synthesize internalLabel = _internalLabel;
#synthesize textShadowColor = _textShadowColor;
#synthesize textShadowOffset = _textShadowOffset;
- (id)initWithFrame:(CGRect)frame
font:(UIFont *)font
textColor:(UIColor *)textColor
shadowColor:(UIColor *)shadowColor
shadowOffset:(CGSize)shadowOffset
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
// register to my own text changes notification, so I can update the internal label
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(handleUITextFieldTextDidChangeNotification)
name:UITextFieldTextDidChangeNotification
object:nil];
self.font = font;
self.textColor = textColor;
self.textShadowColor = shadowColor;
self.textShadowOffset = shadowOffset;
}
return self;
}
// when the user enter text we update the internal label
- (void)handleUITextFieldTextDidChangeNotification
{
self.internalLabel.text = self.text;
[self.internalLabel sizeToFit];
}
// init the internal label when first needed
- (UILabel *)internalLabel
{
if (!_internalLabel) {
_internalLabel = [[UILabel alloc] initWithFrame:self.bounds];
[self addSubview:_internalLabel];
_internalLabel.font = self.font;
_internalLabel.backgroundColor = [UIColor clearColor];
}
return _internalLabel;
}
// override this method to update the internal label color
// and to set the original label to clear so we wont get two labels
- (void)setTextColor:(UIColor *)textColor
{
[super setTextColor:[UIColor clearColor]];
self.internalLabel.textColor = textColor;
}
// override this method to update the internal label text
- (void)setText:(NSString *)text
{
[super setText:text];
self.internalLabel.text = self.text;
[self.internalLabel sizeToFit];
}
- (void)setTextShadowColor:(UIColor *)textShadowColor
{
self.internalLabel.shadowColor = textShadowColor;
}
- (void)setTextShadowOffset:(CGSize)textShadowOffset
{
self.internalLabel.shadowOffset = textShadowOffset;
}
- (void)drawTextInRect:(CGRect)rect {
// don't draw anything
// we have the internal label for that...
}
- (void)dealloc {
[_internalLabel release];
[_textShadowColor release];
[super dealloc];
}
#end
Here is how you use it in your view controller
- (void)viewDidLoad
{
[super viewDidLoad];
ShadowTextField *textField = [[ShadowTextField alloc] initWithFrame:CGRectMake(0, 0, 320, 30)
font:[UIFont systemFontOfSize:22.0]
textColor:[UIColor whiteColor]
shadowColor:[UIColor redColor]
shadowOffset:CGSizeMake(0, 1) ] ;
textField.text = #"This is some text";
textField.backgroundColor = [UIColor blackColor];
[self.view addSubview:textField];
}
You could try to do the drawing of the label yourself. Remove
[super drawTextInRect:rect]
And instead draw your own label. I haven't tried this but it could look something like this:
// Declare a label as a member in your class in the .h file and a property for it:
UILabel *textFieldLabel;
#property (nonatomic, retain) UILabel *textFieldLabel;
// Draw the label
- (void)drawTextInRect:(CGRect)rect {
if (self.textFieldLabel == nil) {
self.textFieldLabel = [[[UILabel alloc] initWithFrame:rect] autorelease];
[self.view addSubview:myLabel];
}
self.textFieldLabel.frame = rect;
self.textFieldLabel.text = self.text;
/** Set the style you wish for your label here **/
self.textFieldLabel.shadowColor = [UIColor grayColor];
self.textFieldLabel.shadowOffset = CGSizeMake(2,2);
self.textFieldLabel.textColor = [UIColor blueColor];
// Do not call [super drawTextInRect:myLabel] method if drawing your own text
}
Stop calling super and render the text yourself.
Have your tried with the standard shadow properties of the CALayer? it usually is enough and it is lot simpler. Try something like this with a regular UITextField:
self.inputContainer.layer.shadowColor=[UIColor blackColor].CGColor;
self.inputContainer.layer.shadowRadius=8.0f;
self.inputContainer.layer.cornerRadius=8.0f;
self.inputContainer.layer.shadowOffset=CGSizeMake(0, 4);
You need to import QuartzCore first of course!
#import <QuartzCore/QuartzCore.h>
I wrote this code to create a custom annotation image
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
static NSString *google = #"googlePin";
if ([annotation isKindOfClass:[myClass class]])
{
MKAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:google];
if (!annotationView)
{
annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:google];
annotationView.image = [UIImage imageNamed:#"pin.png"];
}
return annotationView;
}
return nil;
}
The image is appearing on the map; however when I click on it nothing happen, no title nor subtitle.
Do you guys have any idea?
When you override viewForAnnotation, you have to set canShowCallout to YES (the default on a new view you alloc/init is NO).
If you don't override that delegate method, the map view creates a default red pin with canShowCallout already set to YES.
However, even with canShowCallout set to YES, the callout will still not appear if the annotation's title is nil or blank (empty string).
(But again, if the title is not nil and not blank, the callout won't show unless canShowCallout is YES.)
MKAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:google];
if (!annotationView)
{
annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:google];
annotationView.image = [UIImage imageNamed:#"pin.png"];
annotationView.canShowCallout = YES; // <-- add this
}
else
{
// unrelated but should handle view re-use...
annotationView.annotation = annotation;
}
return annotationView;
I manages to integrate Icon Overlay like dropbox in Mac Os Finder with a SIMBL plugin !
I Use the swizzle method to override some finder function.
Here is my swizzle method :
void PluginSwizzleInstanceMethod(Class cls, SEL oldSel, SEL newSel)
{
Method mnew = class_getInstanceMethod(cls, newSel);
Method mold = class_getInstanceMethod(cls, oldSel);
method_exchangeImplementations(mnew, mold);
}
So for the moment i override the drawWithFrame method like this :
Class cls = NSClassFromString(#"TIconAndTextCell");
SEL oldSel = #selector(drawWithFrame:inView:);
SEL newSel = #selector(PluginDrawWithFrame:inView:);
PluginSwizzleInstanceMethod(cls, oldSel, newSel);
all my icons are displayed but not on my desktop or on the view that show items as icon ...
Why ?
thank you ,
I just managed to do some stuff for the Desktop, so I'll post in case that's still relevant for someone:
So as you experienced, Finder draws icons differently on the Desktop.
One class that is responsible for that is TDesktopIcon.
One can, for example, change icons by overriding the drawIconInContext: method, and setting the thumbnailImage property to whatever NSImage* he likes.
- (void) FO_drawIconInContext:(struct CGContext *)arg1 {
NSImage *image;
// set image...
[(TDesktopIcon*)self setThumbnailImage:image];
[self FO_drawIconInContext:arg1];
}
Getting the url for each item was not as straight forward as with TIconAndTextCell or IKImageBrowserCell.
In order to get the URLs, you need to override the prepareToDrawNode: method of TDesktopViewController.
You get the URL from arg1 as follows:
- (void) FO_prepareToDrawNode:(const struct TFENode *)arg1 {
struct OpaqueNodeRef *opr = arg1->fNodeRef;
struct FINode *fiNode = [FINode nodeFromNodeRef:opr];
NSURL *url = [fiNode previewItemURL];
// save url somewhere (I use NSUserDefaults)
[self FO_prepareToDrawNode:arg1];
}
Maybe there's a better way, but that's what I figured out...
Got it! (THIS CODE ONLY WORKS FOR 10.7 THOUGH) I was having issues because the icon was being drawn before the final image was rendered to the thumbnail image.... so I got these artifacts where the original icon was missing....but the overlay was always there...it was the order of draw operations I had wrong...!
I even have the name for the target node... you need the TFENodeHelper class to translate the TFENode into a filePath...
//
// DesktopViewIconOverlay.h
// FinderIconOverlayExample
//
// Created by Orbitus007 on 2/20/13.
//
//
#import <Foundation/Foundation.h>
#import <Quartz/Quartz.h>
#import "Finder.h"
#interface DesktopViewIconOverlay : NSObject
+ (void)pluginLoad;
- (void) FO_prepareToDrawNode:(const struct TFENode *)arg1;
- (void) FO_drawIconInContext:(struct CGContext *)arg1;
#end
//
// DesktopViewIconOverlay.m
// FinderIconOverlayExample
//
// Created by Orbitus007 on 2/20/13.
//
//
#import "DesktopViewIconOverlay.h"
#import "FinderIconOverlayExample.h"
#include <objc/objc.h>
#include <objc/runtime.h>
#import <Quartz/Quartz.h>
#import "TFENodeHelper.h"
static TFENodeHelper *gNodeHelper;
#implementation DesktopViewIconOverlay
+ (void)pluginLoad
{
// Create helper object
gNodeHelper = [[TFENodeHelper alloc] init];
if (gNodeHelper == nil) {
NSLog(#"Failed to instantiate 'TFENodeHelper' class");
return;
}
Method old, new;
Class self_class = [self class];
Class finder_class = [objc_getClass("TDesktopIcon") class];
class_addMethod(finder_class, #selector(FO_drawIconInContext:),
class_getMethodImplementation(self_class, #selector(FO_drawIconInContext:)),"v#:#");
old = class_getInstanceMethod(finder_class, #selector(drawIconInContext:));
new = class_getInstanceMethod(finder_class, #selector(FO_drawIconInContext:));
method_exchangeImplementations(old, new);
finder_class = [objc_getClass("TDesktopViewController") class];
class_addMethod(finder_class, #selector(FO_prepareToDrawNode:),
class_getMethodImplementation(self_class, #selector(FO_prepareToDrawNode:)),"v#:#");
old = class_getInstanceMethod(finder_class, #selector(prepareToDrawNode:));
new = class_getInstanceMethod(finder_class, #selector(FO_prepareToDrawNode:));
method_exchangeImplementations(old, new);
}
- (void) FO_prepareToDrawNode:(const struct TFENode *)arg1 {
NSString *path = [gNodeHelper pathForNode:arg1];
NSLog(#"Path = %#", path);
[[NSUserDefaults standardUserDefaults] setValue:path forKey:#"TDesktopIconURL"];
//struct OpaqueNodeRef *opr = arg1->fNodeRef;
//NSLog(#"path = %#", [[FINode nodeWithFENode:arg1] fullPath]);
//id fiNode = [FINode nodeFromNodeRef:opr];
//NSURL *url = [fiNode previewItemURL];
//[[NSUserDefaults standardUserDefaults] setValue:[url path] forKey:#"TDesktopIconURL"];
//NSLog(#"sending ", arg1->fNodeRef);
// save url somewhere (I use NSUserDefaults)
[self FO_prepareToDrawNode:arg1];
}
- (void) FO_drawIconInContext:(struct CGContext *)arg1
{
[self FO_drawIconInContext:arg1];
NSString *iconPath = [[NSUserDefaults standardUserDefaults] valueForKey:#"TDesktopIconURL"];
NSLog(#"recieved %#", iconPath);
if (![iconPath isEqualToString:#"/Users/h0xff/Desktop/Restart Finder.app"])
return;
NSString *imagePath = #"/Users/h0xff/Desktop/020513_icons/128_synced_green.png";
NSImage *overlay = [[NSImage alloc] initWithContentsOfFile:imagePath];
NSImage *mainImage = [(TDesktopIcon*)self thumbnailImage];
float width = 256.0;
float height = 256.0;
NSImage *finalImage = [[NSImage alloc] initWithSize:NSMakeSize(width, height)];
[finalImage lockFocus];
// draw the base image
[mainImage drawInRect:NSMakeRect(0, 0, width, height)
fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0];
// draw the overlay image at some offset point
[overlay drawInRect:NSMakeRect(0, 0, [overlay size].width/1.5, [overlay size].height/1.5)
fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0];
[finalImage unlockFocus];
// set image...
[(TDesktopIcon*)self setThumbnailImage:finalImage];
[self FO_drawIconInContext:arg1];
}
#end
This code was derived from Alexey Zhuchkov and many others.... please ask questions if you have any!
//
// TFENodeHelper.h
// FinderMenu
//
// Helper class to get full path from TFENode
//
// Created by Alexey Zhuchkov on 11/4/12.
// Copyright (c) 2012 InfiniteLabs. All rights reserved.
//
#import <Foundation/Foundation.h>
// Forward declarations
struct TFENode;
typedef enum {
TFENodeCtorTypeUnknown,
TFENodeCtorTypeNodeWithFENode, // nodeWithFENode
TFENodeCtorTypeNodeFromNodeRef, // nodeFromNodeRef
} TFENodeCtorType;
typedef enum {
TFENodePathMethodTypeUnknown,
TFENodePathMethodTypeFullPath, // fullPath
TFENodePathMethodTypePreviewItemURL, // previewItemURL
} TFENodePathMethodType;
#interface TFENodeHelper : NSObject {
#private
Class _class;
TFENodeCtorType _ctorType;
TFENodePathMethodType _pathMethodType;
}
- (NSString *)pathForNode:(const struct TFENode *)node;
#end
//
// TFENodeHelper.m
// FinderMenu
//
// Created by Alexey Zhuchkov on 11/4/12.
// Copyright (c) 2012 InfiniteLabs. All rights reserved.
//
#import "TFENodeHelper.h"
#import <objc/runtime.h>
#import "Finder.h"
#implementation TFENodeHelper
- (id)init {
self = [super init];
if (self) {
_class = NSClassFromString(#"FINode");
_ctorType = TFENodeCtorTypeUnknown;
_pathMethodType = TFENodePathMethodTypeUnknown;
if (_class) {
if (class_getClassMethod(_class, #selector(nodeWithFENode:))) {
_ctorType = TFENodeCtorTypeNodeWithFENode;
} else if (class_getClassMethod(_class, #selector(nodeFromNodeRef:))) {
_ctorType = TFENodeCtorTypeNodeFromNodeRef;
}
if (class_getInstanceMethod(_class, #selector(fullPath))) {
_pathMethodType = TFENodePathMethodTypeFullPath;
} else if (class_getInstanceMethod(_class, #selector(previewItemURL))) {
_pathMethodType = TFENodePathMethodTypePreviewItemURL;
}
}
if (!_class
|| (_ctorType == TFENodePathMethodTypeUnknown)
|| (_pathMethodType == TFENodePathMethodTypeUnknown)) {
[self release];
return nil;
}
}
return self;
}
- (NSString *)pathForNode:(const struct TFENode *)node {
FINode *fiNode = nil;
NSString *path = nil;
switch (_ctorType) {
case TFENodeCtorTypeNodeWithFENode:
fiNode = [_class nodeWithFENode:node];
break;
case TFENodeCtorTypeNodeFromNodeRef:
fiNode = [_class nodeFromNodeRef:node->fNodeRef];
break;
default:
break;
}
NSURL *url;
if (fiNode) {
switch (_pathMethodType) {
case TFENodePathMethodTypeFullPath:
path = [fiNode fullPath];
break;
case TFENodePathMethodTypePreviewItemURL:
url = [fiNode previewItemURL];
path = [url path];
default:
break;
}
}
return path;
}
#end
In a CALayer subclass I'm working on I have a custom property that I want to animate automatically, that is, assuming the property is called "myProperty", I want the following code:
[myLayer setMyProperty:newValue];
To cause a smooth animation from the current value to "newValue".
Using the approach of overriding actionForKey: and needsDisplayForKey: (see following code) I was able to get it to run very nicely to simply interpolate between the old and and new value.
My problem is that I want to use a slightly different animation duration or path (or whatever) depending on both the current value and the new value of the property and I wasn't able to figure out how to get the new value from within actionForKey:
Thanks in advance
#interface ERAnimatablePropertyLayer : CALayer {
float myProperty;
}
#property (nonatomic, assign) float myProperty;
#end
#implementation ERAnimatablePropertyLayer
#dynamic myProperty;
- (void)drawInContext:(CGContextRef)ctx {
... some custom drawing code based on "myProperty"
}
- (id <CAAction>)actionForKey:(NSString *)key {
if ([key isEqualToString:#"myProperty"]) {
CABasicAnimation *theAnimation = [CABasicAnimation animationWithKeyPath:key];
theAnimation.fromValue = [[self presentationLayer] valueForKey:key];
... I want to do something special here, depending on both from and to values...
return theAnimation;
}
return [super actionForKey:key];
}
+ (BOOL)needsDisplayForKey:(NSString *)key {
if ([key isEqualToString:#"myProperty"])
return YES;
return [super needsDisplayForKey:key];
}
#end
You need to avoid custom getters and setters for properties you want to animate.
Override the didChangeValueForKey: method. Use it to set the model value for the property you want to animate.
Don't set the toValue on the action animation.
#interface MyLayer: CALayer
#property ( nonatomic ) NSUInteger state;
#end
-
#implementation MyLayer
#dynamic state;
- (id<CAAction>)actionForKey: (NSString *)key {
if( [key isEqualToString: #"state"] )
{
CABasicAnimation * bgAnimation = [CABasicAnimation animationWithKeyPath: #"backgroundColor"];
bgAnimation.fromValue = [self.presentationLayer backgroundColor];
bgAnimation.duration = 0.4;
return bgAnimation;
}
return [super actionForKey: key];
}
- (void)didChangeValueForKey: (NSString *)key {
if( [key isEqualToString: #"state"] )
{
const NSUInteger state = [self valueForKey: key];
UIColor * newBackgroundColor;
switch (state)
{
case 0:
newBackgroundColor = [UIColor redColor];
break;
case 1:
newBackgroundColor = [UIColor blueColor];
break;
case 2:
newBackgroundColor = [UIColor greenColor];
break;
default:
newBackgroundColor = [UIColor purpleColor];
}
self.backgroundColor = newBackgroundColor.CGColor;
}
[super didChangeValueForKey: key];
}
#end
Core Animation calls actionForKey: before updating the property value. It runs the action after updating the property value by sending it runActionForKey:object:arguments:. The CAAnimation implementation of runActionForKey:object:arguments: just calls [object addAnimation:self forKey:key].
Instead of returning the animation from actionForKey:, you can return a CAAction that, when run, creates and installs an animation. Something like this:
#interface MyAction: NSObject <CAAction>
#property (nonatomic, strong) id priorValue;
#end
#implementation MyAction
- (void)runActionForKey:(NSString *)key object:(id)anObject arguments:(NSDictionary *)dict {
ERAnimatablePropertyLayer *layer = anObject;
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:key];
id newValue = [layer valueForKey:key];
// Set up animation using self.priorValue and newValue to determine
// fromValue and toValue. You could use a CAKeyframeAnimation instead of
// a CABasicAnimation.
[layer addAnimation:animation forKey:key];
}
#end
#implementation ERAnimatablePropertyLayer
- (id <CAAction>)actionForKey:(NSString *)key {
if ([key isEqualToString:#"myProperty"]) {
MyAction *action = [[MyAction alloc] init];
action.priorValue = [self valueForKey:key];
return action;
}
return [super actionForKey:key];
}
You can find a working example of a similar technique (in Swift, animating cornerRadius) in this answer.
You can store the old and new values in CATransaction.
-(void)setMyProperty:(float)value
{
NSNumber *fromValue = [NSNumber numberWithFloat:myProperty];
[CATransaction setValue:fromValue forKey:#"myPropertyFromValue"];
myProperty = value;
NSNumber *toValue = [NSNumber numberWithFloat:myProperty];
[CATransaction setValue:toValue forKey:#"myPropertyToValue"];
}
- (id <CAAction>)actionForKey:(NSString *)key {
if ([key isEqualToString:#"myProperty"]) {
CABasicAnimation *theAnimation = [CABasicAnimation animationWithKeyPath:key];
theAnimation.fromValue = [[self presentationLayer] valueForKey:key];
theAnimation.toValue = [CATransaction objectForKey:#"myPropertyToValue"];
// here you do something special.
}
return [super actionForKey:key];
}