Creating a dynamic MKAnnotationView in Swift - mapkit

Currently, I know how to create a MKAnnotationView with a static pin, an image that's added.
Does anyone know, or have any resources, on how to create a pin that would change colors or have a number displayed inside of it that would change depending on information about the business?
For instance, I would like to have a pin be red when the business is closed, and green when the business is open. Maybe even a dollar signs inside of the pin to tell the user how expensive it is.
EDIT
I have created a class called CustomPin that adopts the MKAnnotation protocol. Additionally, I'm not looking to have a custom MKAnnotationView image that can change. Does that mean I'll have to add multiple images for the MKAnnotationView in an array and have it change the image everytime the details about the business change?
Thank you in advance!

You can create custom MKAnnotationView by simply inheritance. You can use this class to create annotation view from delegate method.
Here is one example.
class KDAnnotationView: MKAnnotationView {
let titleLabel = UILabel()
convenience init(annotation: MKAnnotation?) {
self.init(annotation: annotation, reuseIdentifier: "indetifier")
self.canShowCallout = false
self.frame = CGRectMake(0, 0, 75.0, 85.0)
self.backgroundColor = UIColor.clearColor()
self.centerOffset = CGPointMake(0, 0)
self.titleLabel.backgroundColor = UIColor.clearColor()
self.titleLabel.textColor = UIColor.blackColor()
self.titleLabel.font = UIFont.systemFontOfSize(16.0)
self.addSubview(self.titleLabel)
}
override func layoutSubviews() {
super.layoutSubviews()
var frame = CGRectInset(self.bounds, 5.0, 5.0)
frame.size.height = 20.0
self.titleLabel.frame = frame
}
override func drawRect(rect: CGRect) {
super.drawRect(rect)
let path = UIBezierPath()
path.moveToPoint(CGPoint(x: CGRectGetMinX(rect), y: CGRectGetMinY(rect)))
path.addLineToPoint(CGPoint(x: CGRectGetMaxX(rect), y: CGRectGetMinY(rect)))
path.addLineToPoint(CGPoint(x: CGRectGetMaxX(rect), y: CGRectGetMaxY(rect) - 10.0))
path.addLineToPoint(CGPoint(x: CGRectGetMidX(rect) + 5.0, y: CGRectGetMaxY(rect) - 10.0))
path.addLineToPoint(CGPoint(x: CGRectGetMidX(rect), y: CGRectGetMaxY(rect)))
path.addLineToPoint(CGPoint(x: CGRectGetMidX(rect) - 5.0, y: CGRectGetMaxY(rect) - 10.0))
path.addLineToPoint(CGPoint(x: CGRectGetMinX(rect), y: CGRectGetMaxY(rect) - 10.0))
path.closePath()
UIColor.lightGrayColor().setStroke()
UIColor.whiteColor().setFill()
path.stroke()
path.fill()
}
//MARK: - Public Methods
func setText(text:String) {
self.titleLabel.text = text
}
}

You have to do this programmatically. Following code may help you:
Initialize the pin and give it a type, such as opened, closed, expensive etc.
MapViewAnnotation *newAnnotation = [[MapViewAnnotation alloc] init...
newAnnotation.type = #"opened"; // <-- set type in annotation
[self.mapView addAnnotation:newAnnotation];
In your (MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id ) annotation method put a simple check
MapViewAnnotation *mvAnn = (MapViewAnnotation *)annotation;
if ([mvAnn.type isEqualToString:#"opened"])
{
annView.pinColor = MKPinAnnotationColorGreen;
}
else if ([mvAnn.type isEqualToString:#"closed"])
{
annView.pinColor = MKPinAnnotationColorRed;
}
else
{
annView.pinColor = MKPinAnnotationColorOrange;
}

Related

How to draw a dash line border for NSView

In my custom view, i have code as below:
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
//Drawing code here.
[self setWantsLayer: YES];
[self.layer setBorderWidth: 1];
[self.layer setBorderColor:[NSColor colorWithRed:205/255.0 green:211/255.0 blue:232/255.0 alpha:1.0].CGColor];
[self.layer setCornerRadius: 10];
}
This is OK to set border line and color for my NSView, but i want to set a dash line, anyone know how to do this?
And i tried some codes from the web search, but it doens't draw a border at all.
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
// Drawing code here.
CGFloat dashPattern[] = {10,4}; //make your pattern here
NSBezierPath *textViewSurround = [NSBezierPath bezierPathWithRoundedRect:self.frame xRadius:10 yRadius:10];
[textViewSurround setLineWidth:2.0f];
[textViewSurround setLineDash:dashPattern count:2 phase:0];
[[NSColor colorWithRed:205/255.0 green:211/255.0 blue:232/255.0 alpha:1.0] set];
[textViewSurround stroke];
}
here is a complete example using a subclass of NSView in Swift 3:
class BorderedView: NSView {
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
// dash customization parameters
let dashHeight: CGFloat = 3
let dashLength: CGFloat = 10
let dashColor: NSColor = .red
// setup the context
let currentContext = NSGraphicsContext.current()!.cgContext
currentContext.setLineWidth(dashHeight)
currentContext.setLineDash(phase: 0, lengths: [dashLength])
currentContext.setStrokeColor(dashColor.cgColor)
// draw the dashed path
currentContext.addRect(bounds.insetBy(dx: dashHeight, dy: dashHeight))
currentContext.strokePath()
}
}
In case you want to setup line border with CAShapeLayer (Swift 4.2):
class StrokeWithDashedLineView: NSView {
private let shapeLayer = CAShapeLayer()
private let fillLayer = CALayer()
private let textLabel = NSTextField().autolayoutView()
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
setupUI()
setupLayout()
}
required init?(coder decoder: NSCoder) {
fatalError()
}
override var intrinsicContentSize: NSSize {
return CGSize(intrinsicHeight: 76)
}
override func layout() {
super.layout()
updateLayers()
}
private func updateLayers() {
layer?.cornerRadius = 0.5 * bounds.height // Making ourselves rounded.
// Stroke Layer
let shapeBounds = CGRect(width: bounds.width - shapeLayer.lineWidth, height: bounds.height - shapeLayer.lineWidth)
let shapeRadius = 0.5 * shapeBounds.height
let path = CGMutablePath()
path.addRoundedRect(in: shapeBounds, cornerWidth: shapeRadius, cornerHeight: shapeRadius)
shapeLayer.path = path
shapeLayer.bounds = shapeBounds
shapeLayer.position = CGPoint(x: 0.5 * shapeLayer.lineWidth, y: 0.5 * shapeLayer.lineWidth)
// Fill Layer
let fillBounds = CGRect(width: bounds.width - 2 * shapeLayer.lineWidth, height: bounds.height - 2 * shapeLayer.lineWidth)
fillLayer.cornerRadius = 0.5 * fillBounds.height
fillLayer.bounds = fillBounds
fillLayer.position = CGPoint(x: shapeLayer.lineWidth, y: shapeLayer.lineWidth)
}
private func setupUI() {
wantsLayer = true
layer?.masksToBounds = true
shapeLayer.lineWidth = 3
shapeLayer.strokeColor = NSColor.red.cgColor
shapeLayer.fillColor = nil
shapeLayer.lineDashPattern = [11.2, 11.2]
shapeLayer.lineCap = .round
shapeLayer.anchorPoint = .zero
fillLayer.backgroundColor = NSColor.yellow.cgColor
fillLayer.anchorPoint = .zero
layer?.addSublayer(shapeLayer)
layer?.addSublayer(fillLayer)
addSubview(textLabel)
textLabel.text = "Drag Xib or Storyboard files onto\nthis window to open them"
textLabel.alignment = .center
textLabel.textColor = .black
textLabel.font = NSFont.semibold(size: 13)
textLabel.isEditable = false
textLabel.drawsBackground = false
textLabel.isBezeled = false
}
private func setupLayout() {
textLabel.centerXAnchor.constraint(equalTo: centerXAnchor).activate()
textLabel.centerYAnchor.constraint(equalTo: centerYAnchor).activate()
}
}
Result:
You can do this through CGContext Here is an answer that worked for me:
how to make dashed line moveable
And my result:
You can do this like,
[yourView.layer setBorderWidth:5.0];
[yourView.layer setBorderColor:[[UIColor colorWithPatternImage:[UIImage imageNamed:#"DotedImage.png"]] CGColor]];
Add dashed image in project and import QuartzCore/QuartzCore.hin project,
#import <QuartzCore/QuartzCore.h>
Update :
Image size and View size should be same.

Animate a non-UI property in Objective-C (Mac OS X) [duplicate]

On UIView you can change the backgroundColour animated. And on a UISlideView you can change the value animated.
Can you add a custom property to your own UIView subclass so that it can be animated?
If I have a CGPath within my UIView then I can animate the drawing of it by changing the percentage drawn of the path.
Can I encapsulate that animation into the subclass.
i.e. I have a UIView with a CGPath that creates a circle.
If the circle is not there it represents 0%. If the circle is full it represents 100%. I can draw any value by changing the percentage drawn of the path. I can also animate the change (within the UIView subclass) by animating the percentage of the CGPath and redrawing the path.
Can I set some property (i.e. percentage) on the UIView so that I can stick the change into a UIView animateWithDuration block and it animate the change of the percentage of the path?
I hope I have explained what I would like to do well.
Essentially, all I want to do is something like...
[UIView animateWithDuration:1.0
animations:^{
myCircleView.percentage = 0.7;
}
completion:nil];
and the circle path animate to the given percentage.
If you extend CALayer and implement your custom
- (void) drawInContext:(CGContextRef) context
You can make an animatable property by overriding needsDisplayForKey (in your custom CALayer class) like this:
+ (BOOL) needsDisplayForKey:(NSString *) key {
if ([key isEqualToString:#"percentage"]) {
return YES;
}
return [super needsDisplayForKey:key];
}
Of course, you also need to have a #property called percentage. From now on you can animate the percentage property using core animation. I did not check whether it works using the [UIView animateWithDuration...] call as well. It might work. But this worked for me:
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"percentage"];
animation.duration = 1.0;
animation.fromValue = [NSNumber numberWithDouble:0];
animation.toValue = [NSNumber numberWithDouble:100];
[myCustomLayer addAnimation:animation forKey:#"animatePercentage"];
Oh and to use yourCustomLayer with myCircleView, do this:
[myCircleView.layer addSublayer:myCustomLayer];
Complete Swift 3 example:
public class CircularProgressView: UIView {
public dynamic var progress: CGFloat = 0 {
didSet {
progressLayer.progress = progress
}
}
fileprivate var progressLayer: CircularProgressLayer {
return layer as! CircularProgressLayer
}
override public class var layerClass: AnyClass {
return CircularProgressLayer.self
}
override public func action(for layer: CALayer, forKey event: String) -> CAAction? {
if event == #keyPath(CircularProgressLayer.progress),
let action = action(for: layer, forKey: #keyPath(backgroundColor)) as? CAAnimation,
let animation: CABasicAnimation = (action.copy() as? CABasicAnimation) {
animation.keyPath = #keyPath(CircularProgressLayer.progress)
animation.fromValue = progressLayer.progress
animation.toValue = progress
self.layer.add(animation, forKey: #keyPath(CircularProgressLayer.progress))
return animation
}
return super.action(for: layer, forKey: event)
}
}
/*
* Concepts taken from:
* https://stackoverflow.com/a/37470079
*/
fileprivate class CircularProgressLayer: CALayer {
#NSManaged var progress: CGFloat
let startAngle: CGFloat = 1.5 * .pi
let twoPi: CGFloat = 2 * .pi
let halfPi: CGFloat = .pi / 2
override class func needsDisplay(forKey key: String) -> Bool {
if key == #keyPath(progress) {
return true
}
return super.needsDisplay(forKey: key)
}
override func draw(in ctx: CGContext) {
super.draw(in: ctx)
UIGraphicsPushContext(ctx)
//Light Grey
UIColor.lightGray.setStroke()
let center = CGPoint(x: bounds.midX, y: bounds.midY)
let strokeWidth: CGFloat = 4
let radius = (bounds.size.width / 2) - strokeWidth
let path = UIBezierPath(arcCenter: center, radius: radius, startAngle: 0, endAngle: twoPi, clockwise: true)
path.lineWidth = strokeWidth
path.stroke()
//Red
UIColor.red.setStroke()
let endAngle = (twoPi * progress) - halfPi
let pathProgress = UIBezierPath(arcCenter: center, radius: radius, startAngle: startAngle, endAngle: endAngle , clockwise: true)
pathProgress.lineWidth = strokeWidth
pathProgress.lineCapStyle = .round
pathProgress.stroke()
UIGraphicsPopContext()
}
}
let circularProgress = CircularProgressView(frame: CGRect(x: 0, y: 0, width: 80, height: 80))
UIView.animate(withDuration: 2, delay: 0, options: .curveEaseInOut, animations: {
circularProgress.progress = 0.76
}, completion: nil)
There is a great objc article here, which goes into details about how this works
As well as a objc project that uses the same concepts here:
Essentially action(for layer:) will be called when an object is being animated from an animation block, we can start our own animations with the same properties (stolen from the backgroundColor property) and animate the changes.
For the ones who needs more details on that like I did:
there is a cool example from Apple covering this question.
E.g. thanks to it I found that you don't actually need to add your custom layer as sublayer (as #Tom van Zummeren suggests). Instead it's enough to add a class method to your View class:
+ (Class)layerClass
{
return [CustomLayer class];
}
Hope it helps somebody.
you will have to implement the percentage part yourself. you can override layer drawing code to draw your cgpath accroding to the set percentage value. checkout the core animation programming guide and animation types and timing guide
#David Rees answer get me on the right track, but there is one issue. In my case
completion of animation always returns false, right after animation has began.
UIView.animate(withDuration: 2, delay: 0, options: .curveEaseInOut, animations: {
circularProgress.progress = 0.76
}, completion: { finished in
// finished - always false
})
This is the way it've worked for me - action of animation is handled inside of CALayer.
I have also included small example how to make layer with additional properties like "color".
In this case, without initializer that copies the values, changing the color would take affect only on non-animating view. During animation it would be visble with "default setting".
public class CircularProgressView: UIView {
#objc public dynamic var progress: CGFloat {
get {
return progressLayer.progress
}
set {
progressLayer.progress = newValue
}
}
fileprivate var progressLayer: CircularProgressLayer {
return layer as! CircularProgressLayer
}
override public class var layerClass: AnyClass {
return CircularProgressLayer.self
}
}
/*
* Concepts taken from:
* https://stackoverflow.com/a/37470079
*/
fileprivate class CircularProgressLayer: CALayer {
#NSManaged var progress: CGFloat
let startAngle: CGFloat = 1.5 * .pi
let twoPi: CGFloat = 2 * .pi
let halfPi: CGFloat = .pi / 2
var color: UIColor = .red
// preserve layer properties
// without this specyfic init, if color was changed to sth else
// animation would still use .red
override init(layer: Any) {
super.init(layer: layer)
if let layer = layer as? CircularProgressLayer {
self.color = layer.color
self.progress = layer.progress
}
}
override init() {
super.init()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override class func needsDisplay(forKey key: String) -> Bool {
if key == #keyPath(progress) {
return true
}
return super.needsDisplay(forKey: key)
}
override func action(forKey event: String) -> CAAction? {
if event == #keyPath(CircularProgressLayer.progress) {
guard let animation = action(forKey: #keyPath(backgroundColor)) as? CABasicAnimation else {
setNeedsDisplay()
return nil
}
if let presentation = presentation() {
animation.keyPath = event
animation.fromValue = presentation.value(forKeyPath: event)
animation.toValue = nil
} else {
return nil
}
return animation
}
return super.action(forKey: event)
}
override func draw(in ctx: CGContext) {
super.draw(in: ctx)
UIGraphicsPushContext(ctx)
//Light Gray
UIColor.lightGray.setStroke()
let center = CGPoint(x: bounds.midX, y: bounds.midY)
let strokeWidth: CGFloat = 4
let radius = (bounds.size.width / 2) - strokeWidth
let path = UIBezierPath(arcCenter: center, radius: radius, startAngle: 0, endAngle: twoPi, clockwise: true)
path.lineWidth = strokeWidth
path.stroke()
// Red - default
self.color.setStroke()
let endAngle = (twoPi * progress) - halfPi
let pathProgress = UIBezierPath(arcCenter: center, radius: radius, startAngle: startAngle, endAngle: endAngle , clockwise: true)
pathProgress.lineWidth = strokeWidth
pathProgress.lineCapStyle = .round
pathProgress.stroke()
UIGraphicsPopContext()
}
}
The way to handle animations differently and copy layer properties I have found in this article:
https://medium.com/better-programming/make-apis-like-apple-animatable-view-properties-in-swift-4349b2244cea

How to add QLPreviewController as Subview in objective C

Is it possible to add QLPreviewController to UIView as sub view.
I tried like this
[self.view addSubview:previewViewController.view]
I also called reloadData
[previewViewController reloadData];
I check with this URL Adding QLPreviewController as subview doesn't load PDF . But I did not understand what is self.pdfPreviewView
Please guide me how I can add QLPreviewController as sub view..
Yes its possible, see the code below:
QLPreviewController* preview = [[QLPreviewController alloc] init];
preview.dataSource = self;
preview.delegate = self;
[self addChildViewController:preview];//*view controller containment
//set the frame from the parent view
CGFloat w= self.quickLookView.frame.size.width;
CGFloat h= self.quickLookView.frame.size.height;
preview.view.frame = CGRectMake(0, 0,w, h);
[self.quickLookView addSubview:preview.view];
[preview didMoveToParentViewController:self];
//save a reference to the preview controller in an ivar
self.previewController = preview;
Swift 3.x
private var pVC: QLPreviewController?
override func viewDidLoad() {
super.viewDidLoad()
// I do not not why, but it needs to be setup after delay.
DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: setupPreview)
}
private func setupPreview() {
if (pVC != nil) { return }
let preview = QLPreviewController()
preview.dataSource = self
preview.delegate = self
preview.view.frame = CGRect(origin: CGPoint(x: 0, y: 0), size: previewView.frame.size)
previewView.addSubview(preview.view)
preview.didMove(toParentViewController: self)
pVC = preview
}

Best way to change the background color for an NSView

I'm looking for the best way to change the backgroundColor of an NSView. I'd also like to be able to set the appropriate alpha mask for the NSView. Something like:
myView.backgroundColor = [NSColor colorWithCalibratedRed:0.227f
green:0.251f
blue:0.337
alpha:0.8];
I notice that NSWindow has this method, and I'm not a big fan of the NSColorWheel, or NSImage background options, but if they are the best, willing to use.
Yeah, your own answer was right. You could also use Cocoa methods:
- (void)drawRect:(NSRect)dirtyRect {
// set any NSColor for filling, say white:
[[NSColor whiteColor] setFill];
NSRectFill(dirtyRect);
[super drawRect:dirtyRect];
}
In Swift:
class MyView: NSView {
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
// #1d161d
NSColor(red: 0x1d/255, green: 0x16/255, blue: 0x1d/255, alpha: 1).setFill()
dirtyRect.fill()
}
}
An easy, efficient solution is to configure the view to use a Core Animation layer as its backing store. Then you can use -[CALayer setBackgroundColor:] to set the background color of the layer.
- (void)awakeFromNib {
self.wantsLayer = YES; // NSView will create a CALayer automatically
}
- (BOOL)wantsUpdateLayer {
return YES; // Tells NSView to call `updateLayer` instead of `drawRect:`
}
- (void)updateLayer {
self.layer.backgroundColor = [NSColor colorWithCalibratedRed:0.227f
green:0.251f
blue:0.337
alpha:0.8].CGColor;
}
That’s it!
If you are a storyboard lover, here is a way that you don't need any line of code.
Add NSBox as a subview to NSView and adjust NSBox's frame as the same with NSView.
In Storyboard or XIB change Title position to None, Box type to Custom, Border Type to "None", and Border color to whatever you like.
Here is a screenshot:
This is the result:
If you setWantsLayer to YES first, you can directly manipulate the layer background.
[self.view setWantsLayer:YES];
[self.view.layer setBackgroundColor:[[NSColor whiteColor] CGColor]];
Think I figured out how to do it:
- (void)drawRect:(NSRect)dirtyRect {
// Fill in background Color
CGContextRef context = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort];
CGContextSetRGBFillColor(context, 0.227,0.251,0.337,0.8);
CGContextFillRect(context, NSRectToCGRect(dirtyRect));
}
edit/update: Xcode 8.3.1 • Swift 3.1
extension NSView {
var backgroundColor: NSColor? {
get {
guard let color = layer?.backgroundColor else { return nil }
return NSColor(cgColor: color)
}
set {
wantsLayer = true
layer?.backgroundColor = newValue?.cgColor
}
}
}
usage:
let myView = NSView(frame: NSRect(x: 0, y: 0, width: 100, height: 100))
print(myView.backgroundColor ?? "none") // NSView's background hasn't been set yet = nil
myView.backgroundColor = .red // set NSView's background color to red color
print(myView.backgroundColor ?? "none")
view.addSubview(myView)
I went through all of these answers and none of them worked for me unfortunately. However, I found this extremely simple way, after about an hour of searching : )
myView.layer.backgroundColor = CGColorCreateGenericRGB(0, 0, 0, 0.9);
Best Solution :
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
self.wantsLayer = YES;
}
return self;
}
- (void)awakeFromNib
{
float r = (rand() % 255) / 255.0f;
float g = (rand() % 255) / 255.0f;
float b = (rand() % 255) / 255.0f;
if(self.layer)
{
CGColorRef color = CGColorCreateGenericRGB(r, g, b, 1.0f);
self.layer.backgroundColor = color;
CGColorRelease(color);
}
}
In Swift:
override func drawRect(dirtyRect: NSRect) {
NSColor.greenColor().setFill()
NSRectFill(dirtyRect)
super.drawRect(dirtyRect)
}
Use NSBox, which is a subclass of NSView, allowing us to easily style
Swift 3
let box = NSBox()
box.boxType = .custom
box.fillColor = NSColor.red
box.cornerRadius = 5
Without doubt the easiest way, also compatible with Color Set Assets:
Swift:
view.setValue(NSColor.white, forKey: "backgroundColor")
Objective-C:
[view setValue: NSColor.whiteColor forKey: "backgroundColor"];
Interface Builder:
Add a user defined attribute backgroundColor in the interface builder, of type NSColor.
Just set backgroundColor on the layer (after making the view layer backed).
view.wantsLayer = true
view.layer?.backgroundColor = CGColor.white
I tested the following and it worked for me (in Swift):
view.wantsLayer = true
view.layer?.backgroundColor = NSColor.blackColor().colorWithAlphaComponent(0.5).CGColor
In Swift 3, you can create an extension to do it:
extension NSView {
func setBackgroundColor(_ color: NSColor) {
wantsLayer = true
layer?.backgroundColor = color.cgColor
}
}
// how to use
btn.setBackgroundColor(NSColor.gray)
In swift you can subclass NSView and do this
class MyView:NSView {
required init?(coder: NSCoder) {
super.init(coder: coder);
self.wantsLayer = true;
self.layer?.backgroundColor = NSColor.redColor().CGColor;
}
}
This supports changing systemwide appearance (turning dark mode on or off) while the application is running. You can also set the background colour in Interface Builder, if you set the class of the view to BackgroundColorView first.
class BackgroundColorView: NSView {
#IBInspectable var backgroundColor: NSColor? {
didSet { needsDisplay = true }
}
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
wantsLayer = true
}
required init?(coder decoder: NSCoder) {
super.init(coder: decoder)
wantsLayer = true
}
override var wantsUpdateLayer: Bool { return true }
override func updateLayer() {
layer?.backgroundColor = backgroundColor?.cgColor
}
}
Have a look at RMSkinnedView. You can set the NSView's background color from within Interface Builder.
Just small reusable class (Swift 4.1)
class View: NSView {
var backgroundColor: NSColor?
convenience init() {
self.init(frame: NSRect())
}
override func draw(_ dirtyRect: NSRect) {
if let backgroundColor = backgroundColor {
backgroundColor.setFill()
dirtyRect.fill()
} else {
super.draw(dirtyRect)
}
}
}
// Usage
let view = View()
view.backgroundColor = .white

How to implement Magnifier [duplicate]

I would like be able to create a movable magnifier (like the one you have when you copy and paste) in a custom view, for zooming a part of my view.
I have no idea on how to start, do you have any idea?
Thanks in advance for your help :)
We do this in Crosswords. In your drawRect method, mask off a circle (using a monochrome bitmap containing the 'mask' of your magnifying glass) and draw your subject view in there with a 2x scale transform. Then draw a magnifying glass image over that and you're done.
- (void) drawRect: (CGRect) rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect bounds = self.bounds;
CGImageRef mask = [UIImage imageNamed: #"loupeMask"].CGImage;
UIImage *glass = [UIImage imageNamed: #"loupeImage"];
CGContextSaveGState(context);
CGContextClipToMask(context, bounds, mask);
CGContextFillRect(context, bounds);
CGContextScaleCTM(context, 2.0, 2.0);
//draw your subject view here
CGContextRestoreGState(context);
[glass drawInRect: bounds];
}
There is a complete example over here. There is a minor error in the downloaded project but otherwise it works great and does exactly what you need.
I use this code in Swift 3 :
class MagnifyingGlassView: UIView {
var zoom: CGFloat = 2 {
didSet {
setNeedsDisplay()
}
}
weak var readView: UIView?
// MARK: - UIVIew
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
override func draw(_ rect: CGRect) {
guard let readView = readView else { return }
let magnifiedBounds = magnifyBounds(of: readView, zoom: zoom)
readView.drawHierarchy(in: magnifiedBounds, afterScreenUpdates: false)
}
// MARK: - Private
private func setupView() {
isOpaque = false
backgroundColor = UIColor.clear
}
private func magnifyBounds(of view: UIView, zoom: CGFloat) -> CGRect {
let transform = CGAffineTransform(scaleX: zoom, y: zoom)
var bounds = view.bounds.applying(transform)
bounds.center = view.bounds.center
return view.convert(bounds, to: self)
}
}
extension CGRect {
var center: CGPoint {
get {
return CGPoint(x: origin.x + width / 2, y: origin.y + height / 2)
}
set {
origin.x = newValue.x - width / 2
origin.y = newValue.y - height / 2
}
}
}
You need to call setNeedsDisplay in scrollViewDidScroll: if your read view is a scrollView.