I have MKAnnotationView which is drag able, and I have implemented didChangeDragState: delegate method for which I get callback at start and end of drag but not continuously. I want to track the current coordinate of annotation as it is dragged, please help me with some solution. Thank you.
The coordinate can be got from annotationview.coordinates :
-(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view
didChangeDragState:(MKAnnotationViewDragState)newState fromOldState:
(MKAnnotationViewDragState)oldState
{
CLLocationCoordinate2D currentCoordinates = view.annotation.coordinate;
}
As far as I know, there isn't an Apple-provided way for you to do this, but you can achieve it through KVO (h/t to this answer).
You'll also need to manually determine the annotation view's coordinates, as the annotation's coordinates aren't updated until after it has entered MKAnnotationViewDragStateEnding.
The full solution looks like this:
// Somewhere in your code before the annotation view gets dragged, subscribe your observer object to changes in the annotation view's frame center
annotationView.addObserver(DELEGATE_OBJECT, forKeyPath: "center", options: NSKeyValueObservingOptions.New, context: nil)
// Then in the observer's code, fill out the KVO callback method
// Your observer will need to conform to the NSKeyValueObserving protocol -- all `NSObject`s already do
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
// Check that we're observing an annotation view. The `where` clause can be omitted if you're only observing changes in its frame, or don't care about extra calls
if let annotationView = object as? MKAnnotationView where keyPath == "center" {
// Defined below, `coordinateForAnnotationView` converts a CGPoint measured within a given MKMapView to the geographical coordinate represented in that location
let newCoordinate = coordinateForAnnotationView(pin, inMapView: self.mapView)
// Do stuff with `newCoordinate`
}
}
func coordinateForAnnotationView(annotationView: MKAnnotationView, inMapView mapView: MKMapView) -> CLLocationCoordinate2D {
// Most `MKAnnotationView`s will have a center offset, including `MKPinAnnotationView`
let trueCenter = CGPoint(x: annotationView.center.x - annotationView.centerOffset.x,
y: annotationView.center.y - annotationView.centerOffset.y)
return mapView.convertPoint(trueCenter, toCoordinateFromView: mapView)
}
Related
I have a NSCollectionViewFlowLayout which contains the following:
- (NSSize) itemSize
{
return CGSizeMake(self.collectionView.bounds.size.width, kItemHeight);
} // End of itemSize
- (CGFloat) minimumLineSpacing
{
return 0;
} // End of minimumLineSpacing
- (CGFloat) minimumInteritemSpacing
{
return 0;
} // End of minimumInteritemSpacing
I've tried to ways to make the layout responsive (set the width whenever resized). I've tried adding the following:
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(onWindowDidResize:)
name: NSWindowDidResizeNotification
object: nil];
- (void) onWindowDidResize: (NSNotification*) notification
{
[connectionsListView.collectionViewLayout invalidateLayout];
} // End of windowDidResize:
And this works fine if I expand the collection view (resize larger). But if I attempt to collapse the view (resize smaller), I get the following exceptions:
The behavior of the UICollectionViewFlowLayout is not defined because:
The item width must be less than the width of the UICollectionView minus the section insets left and right values, minus the content insets left and right values.
The relevant UICollectionViewFlowLayout instance is <TestListLayout: 0x106f70f90>, and it is attached to <NSCollectionView: 0x106f76480>.
Any suggestions on how I can resolve this?
NOTE1: This is macOS and not iOS (even though the error message states UICollectionViewFlowLayout).
NOTE2: Even though I receive the warning/error the layout width works, but I would like to figure out the underlying issue.
I had the same problem but in my case I resized view. #Giles solution didn't work for me until I changed invalidation context
class MyCollectionViewFlowLayout: NSCollectionViewFlowLayout {
override func shouldInvalidateLayout(forBoundsChange newBounds: NSRect) -> Bool {
return true
}
override func invalidationContext(forBoundsChange newBounds: NSRect) -> NSCollectionViewLayoutInvalidationContext {
let context = super.invalidationContext(forBoundsChange: newBounds) as! NSCollectionViewFlowLayoutInvalidationContext
context.invalidateFlowLayoutDelegateMetrics = true
return context
}
}
Hope it helps someone as it took me couple of evenings to find solution
This is because you are using the didResize event, which is too late. Your items are too wide at the moment the window starts to shrink. Try using:
func shouldInvalidateLayout(forBoundsChange newBounds: NSRect) -> Bool {
return true
}
...when overriding flow layout, to get everything recalculated.
The source code I posted in the question works fine as of macOS 10.14 with no issues. I added the following to my window which displays the collection view.
// Only Mojave and after is resizable. Before that, a full sized collection view caused issues as listed
// at https://stackoverflow.com/questions/48567326/full-width-nscollectionviewflowlayout-with-nscollectionview
if(#available(macOS 10.14, *))
{
self.window.styleMask |= NSWindowStyleMaskResizable;
} // End of macOS 10.14+
I have set up a very simple Single View Application (in Swift) using iOS 8.1. I have added one UIImageView to the main view controller view. I am trying to use CAKeyframeAnimation to animate a sequence of images. I was originally using the UIImageView animationImages property which worked fine but I need to be able to know precisely when the animation finishes, hence the move to CAKeyframeAnimation.
My code is as follows:
class ViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
var animation : CAKeyframeAnimation!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let animationImages:[AnyObject] = [UIImage(named: "image-1")!, UIImage(named: "image-2")!, UIImage(named: "image-3")!, UIImage(named: "image-4")!]
animation = CAKeyframeAnimation(keyPath: "contents")
animation.calculationMode = kCAAnimationDiscrete
animation.duration = 25
animation.values = animationImages
animation.repeatCount = 25
animation.removedOnCompletion = false
animation.fillMode = kCAFillModeForwards
self.imageView.layer.addAnimation(animation, forKey: "contents")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
The problem is that the animation does not display any images and I just receive a blank screen. Is there something I am missing in the above code? How can I get the animation to display?
This line is never going to work:
animation.values = animationImages
Change it to:
animation.values = animationImages.map {$0.CGImage as AnyObject}
The reason is that you are trying to animate the "contents" key of this layer. But that is the contents property. But the contents property must be set to a CGImage, not a UIImage. Your animationImages, by contrast, contains UIImages, not CGImages.
Thus you need to transform your array of UIImage into an array of CGImage. Moreover, you're trying to pass this array to Objective-C, where an NSArray must contain objects only; and since CGImage is not an object in Objective-C's mind, you need to cast each of them as an AnyObject. And that is what my map call does.
I have a UITableViewCell with constraints so that the cells layout correctly on iPhone 6 and iPhone 6+.
The cell contents correctly resize correctly. However, I need to draw a sublayer gradient over one of the views inside. In order to do so, I'm using awakeFromNib to draw the sublayer.
AwakeFromNib is giving the size of the frame prior to it autoresizing and therefore the gradient subview isn't the right size.
If I use layoutSubviews, the first time it's called, the size is also wrong and the cell needs to be scrolled before it has the right size; so that method also isn't working
What method should I be using to get the right frame size in order to draw correctly?
This code worked for me, is written in Swift.
import UIKit
import QuartzCore
class DemoTableViewCell: UITableViewCell {
#IBOutlet weak var gradientView: UIView!
private let gl = CAGradientLayer()
override func awakeFromNib() {
super.awakeFromNib()
// set gradient
let colors: [AnyObject] = [
UIColor.clearColor().CGColor,
UIColor(white: 0, alpha: 0.8).CGColor
]
gl.colors = colors
gradientView.layer.insertSublayer(gl, atIndex: 0)
}
override func layoutSubviews() {
super.layoutSubviews()
// update gradient
gl.frame = gradientView.bounds
}
}
during awakeFromNib the frame layouts do not appear. On your custom Cell you can either do this
-(void)drawRect:(CGRect)rect
{
[super drawRect:rect];
// Enter Custom Code
}
Or you can override applyLayoutAttributes method like this
-(void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
[super applyLayoutAttributes:layoutAttributes];
// Enter custom Code here
}
applyLayoutAttributes is called before drawRect method but still has the size parameteres of rect.
#angshuk answer is perfect where if anyone wants to modify UI inside UITableViewCell. Like in my case I was trying to add a different layer on runtime to an UIView, placed inside UITableViewCell using storyboard/autolayout. Tried the same thing inside awakeFromNib() & layoutSubviews() also. Nothing worked.
Here is the same code using Swift. By the way I am using Xcode 7.3.1 with Swift 2.2.
import UIKit
class InviteMainCellClass: UITableViewCell {
#IBOutlet weak var shareCodeLblSuperView: UIView! //Custom view added using IB/Autolayout
override func awakeFromNib() {
super.awakeFromNib()
//shareCodeLblSuperView.addDashedBorderToView(self.shareCodeLblSuperView, viewBorderColor: UIColor.whiteColor(), borderWidth: 1.0) //Not worked
}
override func drawRect(rect: CGRect) {
super.drawRect(rect)
shareCodeLblSuperView.addDashedBorderToView(self.shareCodeLblSuperView, viewBorderColor: UIColor.whiteColor(), borderWidth: 1.0) //Worked
}
override func layoutSubviews() {
super.layoutSubviews()
//shareCodeLblSuperView.addDashedBorderToView(self.shareCodeLblSuperView, viewBorderColor: UIColor.whiteColor(), borderWidth: 1.0)//Not worked
}
Hope this helped. Thanks.
I have a MKAnnotationView that the user can drag around the map.
It is very difficult for a user to drag the pin. I've tried increasing the frame size and also using a giant custom image. But nothing seems to actually change the hit area for the drag to be larger than default.
Consequently, I have to attempt to tap/drag about ten times before anything happens.
MKAnnotationView *annView = [[[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"bluedot"] autorelease];
UIImage *image = [UIImage imageNamed:#"blue_dot.png"];
annView.image = image;
annView.draggable = YES;
annView.selected = YES;
return annView;
What am I missing here?
EDIT:
It turns out the problem is that MKAnnotationView needs to be touched before you can drag it. I was having trouble because there are a lot of pins nearby and my MKAnnotationView is very small.
I didn't realise MKAnnotationView needed to be touched before you can drag it.
To get around this, I created a timer that selected that MKAnnotationView regularly.
NSTimer *selectAnnotationTimer = [[NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:#selector(selectCenterAnnotation) userInfo:nil repeats:YES] retain];
and the method it calls:
- (void)selectCenterAnnotation {
[mapView selectAnnotation:centerAnnotation animated:NO];
}
Instead of using a timer, you could select the annotation on mouse down. This way you don't mess with the annotation selection, and don't have a timer for each annotation running all the time.
I had the same problem when developing a mac application, and after selecting the annotation on mouse down, the drag works great.
Just came here to say this still helped me today, multiple years later.
In my application, the user can place a bounding area down with annotations and (hopefully) move them around freely. This turned out to be a bit of a nightmare with this "select first and then move" behaviour and just plain made it difficult and infuriating.
To solve this, I default annotation views to selected
func mapView(_ mapView: MKMapView,
viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let view = MKAnnotationView(annotation: annotation, reuseIdentifier: "annotation")
view.isDraggable = true
view.setSelected(true, animated: false)
return view
}
The problem is that there is an annotation manager that resets all annotations back to deselected, so I get around this in this delegate method
func mapView(_ mapView: MKMapView,
annotationView view: MKAnnotationView,
didChange newState: MKAnnotationView.DragState,
fromOldState oldState: MKAnnotationView.DragState) {
if newState == .ending {
mapView.annotations.forEach({
mapview.view(for: $0)?.setSelected(true, animated: false)
})
}
It's a stupid hack for a stupid problem. It does work though.
I have a bunch of UIViews on the screen. I would like to know what's the best to way to check if a particular view (which I have reference to) is intersection ANY other views. The way I am doing it right now is, iterating though all the subviews and check one by one if there's an intersection between the frames.
This doesn't seem very efficient. Is there a better way to do this?
There's a function called CGRectIntersectsRect which receives two CGRects as arguments and returns if the two given rects do intersect. And UIView has subviews property which is an NSArray of UIView objects. So you can write a method with BOOL return value that'll iterate through this array and check if two rectangles intersect, like such:
- (BOOL)viewIntersectsWithAnotherView:(UIView*)selectedView {
NSArray *subViewsInView = [self.view subviews];// I assume self is a subclass
// of UIViewController but the view can be
//any UIView that'd act as a container
//for all other views.
for(UIView *theView in subViewsInView) {
if (![selectedView isEqual:theView])
if(CGRectIntersectsRect(selectedView.frame, theView.frame))
return YES;
}
return NO;
}
To Achieve the same thing in swift as per the accepted answer then here is the function. Readymade Code. Just copy and use it as per the steps. By the way I am using Xcode 7.2 with Swift 2.1.1.
func checkViewIsInterSecting(viewToCheck: UIView) -> Bool{
let allSubViews = self.view!.subviews //Creating an array of all the subviews present in the superview.
for viewS in allSubViews{ //Running the loop through the subviews array
if (!(viewToCheck .isEqual(viewS))){ //Checking the view is equal to view to check or not
if(CGRectIntersectsRect(viewToCheck.frame, viewS.frame)){ //Checking the view is intersecting with other or not
return true //If intersected then return true
}
}
}
return false //If not intersected then return false
}
Now call this function as per the following -
let viewInterSected = self.checkViewIsInterSecting(newTwoPersonTable) //It will give the bool value as true/false. Now use this as per your need
Thanks.
Hope this helped.
In my case the views were nested, so I did this (works with both nested and not):
extension UIView {
func overlaps(other view: UIView, in viewController: UIViewController) -> Bool {
let frame = self.convert(self.bounds, to: viewController.view)
let otherFrame = view.convert(view.bounds, to: viewController.view)
return frame.intersects(otherFrame)
}
}
Hope it helps.
First, create some array that stores the frames of all of your UIViews and their associated reference.
Then, potentially in a background thread, you can run some collision testing using what's in the array. For some simple collision testing for rectangles only, check out this SO question: Simple Collision Algorithm for Rectangles
Hope that helps!