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+
Related
On El Capitan in Xcode 7 beta using Swift 2.0, I subclassed a NSView to use as a prototype view of NSCollectionView's item view, and override the updateTrackingAreas: method to do the mouse tracking. The NSCollectionView was inside a NSPopover.
It seems that only the first 2 times the updateTrackingAreas: will be called, as the debug log shows. The code was like the following:
override func updateTrackingAreas() {
Swift.print("updateTrackingAreas:")
if trackingArea != nil {
self.removeTrackingArea(trackingArea!)
}
trackingArea = NSTrackingArea(
rect: self.bounds,
options: [NSTrackingAreaOptions.MouseEnteredAndExited, NSTrackingAreaOptions.ActiveAlways],
owner: self,
userInfo: nil
)
if trackingArea != nil {
self.addTrackingArea(trackingArea!)
}
var mouseLocation = self.window?.mouseLocationOutsideOfEventStream
mouseLocation = self.convertPoint(mouseLocation!, fromView: nil)
if CGRectContainsPoint(self.bounds, mouseLocation!) {
mouseEntered(NSEvent())
} else {
mouseExited(NSEvent())
}
super.updateTrackingAreas()
}
The first 2 times when the popover was opened, I can see the console log shows updateTrackingAreas:. Then the tracking failed and no logs either.
EDIT:
When I commented out this part like:
//var mouseLocation = self.window?.mouseLocationOutsideOfEventStream
mouseLocation = self.convertPoint(mouseLocation!, fromView: nil)
if CGRectContainsPoint(self.bounds, mouseLocation!) {
mouseEntered(NSEvent())
} else {
mouseExited(NSEvent())
}
The problem will no longer exists.
And the following code makes no differences:
if let window = self.window {
var mouseLocation = window.mouseLocationOutsideOfEventStream
mouseLocation = self.convertPoint(mouseLocation, fromView: nil)
if let event = NSApplication.sharedApplication().currentEvent {
if NSPointInRect(mouseLocation, self.bounds) {
mouseEntered(event)
} else {
mouseExited(event)
}
}
}
EDIT 2:
Ok, I just reinstall OS X Yosemite and compiled it again with Xcode 7 beta 1. The problem no longer exists. Might just be a bug of El Capitan. I'll report it to Apple. Thank you all.
You're passing a newly-created event instance to mouseEntered() and mouseExited(). I realize that this is because you can't pass nil in the Swift variant of the method but perhaps you should pass the current event instead (NSApplication and NSWindow both have a currentEvent() method). Have you tried this?
Checking the mouseLocation in updateTrackingAreas is senseless.
From the docs of updateTrackingAreas:
Invoked automatically when the view’s geometry changes such that its tracking areas need to be recalculated.
This is usually due to frame change. It will be called as many times as you view changes it's frame or position. I doubt you've implemented live-resizing/moving feature on your view, this is the only way mouse inside view's frame can be the source of your view's geometry change.
Ok, I just reinstall OS X Yosemite and compiled it again with Xcode 7 beta 1. The problem no longer exists.
Might just be a bug of El Capitan. Already reported it to Apple.
Thank you all.
I basically want to have my subviews positioned differently depending upon the orientation of the iPad (Portrait or Landscape) using Sizing Classes introduced in xcode 6. I have found numerous tutorials explaining how different sizing classes are available for Iphones in portrait and landscape on the IB but however there seem to be none that cover individual landscape or portrait modes for the iPad on IB. Can anyone help?
It appears to be Apple's intent to treat both iPad orientations as the same -- but as a number of us are finding, there are very legitimate design reasons to want to vary the UI layout for iPad Portrait vs. iPad Landscape.
Unfortunately, the current OS doesn't seem to provide support for this distinction ... meaning that we're back to manipulating auto-layout constraints in code or similar workarounds to achieve what we should ideally be able to get for free using Adaptive UI.
Not an elegant solution.
Isn't there a way to leverage the magic that Apple's already built into IB and UIKit to use a size class of our choosing for a given orientation?
~
In thinking about the problem more generically, I realized that 'size classes' are simply ways to address multiple layouts that are stored in IB, so that they can be called up as needed at runtime.
In fact, a 'size class' is really just a pair of enum values. From UIInterface.h:
typedef NS_ENUM(NSInteger, UIUserInterfaceSizeClass) {
UIUserInterfaceSizeClassUnspecified = 0,
UIUserInterfaceSizeClassCompact = 1,
UIUserInterfaceSizeClassRegular = 2,
} NS_ENUM_AVAILABLE_IOS(8_0);
So regardless of what Apple has decided to name these different variations, fundamentally, they're just a pair of integers used as a unique identifier of sorts, to distinguish one layout from another, stored in IB.
Now, supposing that we create an alternate layout (using a unused size class) in IB -- say, for iPad Portrait ... is there a way to have the device use our choice of size class (UI layout) as needed at runtime?
After trying several different (less elegant) approaches to the problem, I suspected there might be a way to override the default size class programmatically. And there is (in UIViewController.h):
// Call to modify the trait collection for child view controllers.
- (void)setOverrideTraitCollection:(UITraitCollection *)collection forChildViewController:(UIViewController *)childViewController NS_AVAILABLE_IOS(8_0);
- (UITraitCollection *)overrideTraitCollectionForChildViewController:(UIViewController *)childViewController NS_AVAILABLE_IOS(8_0);
Thus, if you can package your view controller hierarchy as a 'child' view controller, and add it to a top-level parent view controller ... then you can conditionally override the child into thinking that it's a different size class than the default from the OS.
Here's a sample implementation that does this, in the 'parent' view controller:
#interface RDTraitCollectionOverrideViewController : UIViewController {
BOOL _willTransitionToPortrait;
UITraitCollection *_traitCollection_CompactRegular;
UITraitCollection *_traitCollection_AnyAny;
}
#end
#implementation RDTraitCollectionOverrideViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self setUpReferenceSizeClasses];
}
- (void)setUpReferenceSizeClasses {
UITraitCollection *traitCollection_hCompact = [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassCompact];
UITraitCollection *traitCollection_vRegular = [UITraitCollection traitCollectionWithVerticalSizeClass:UIUserInterfaceSizeClassRegular];
_traitCollection_CompactRegular = [UITraitCollection traitCollectionWithTraitsFromCollections:#[traitCollection_hCompact, traitCollection_vRegular]];
UITraitCollection *traitCollection_hAny = [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassUnspecified];
UITraitCollection *traitCollection_vAny = [UITraitCollection traitCollectionWithVerticalSizeClass:UIUserInterfaceSizeClassUnspecified];
_traitCollection_AnyAny = [UITraitCollection traitCollectionWithTraitsFromCollections:#[traitCollection_hAny, traitCollection_vAny]];
}
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
_willTransitionToPortrait = self.view.frame.size.height > self.view.frame.size.width;
}
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]
_willTransitionToPortrait = size.height > size.width;
}
-(UITraitCollection *)overrideTraitCollectionForChildViewController:(UIViewController *)childViewController {
UITraitCollection *traitCollectionForOverride = _willTransitionToPortrait ? _traitCollection_CompactRegular : _traitCollection_AnyAny;
return traitCollectionForOverride;
}
#end
As a quick demo to see whether it worked, I added custom labels specifically to the 'Regular/Regular' and 'Compact/Regular' versions of the child controller layout in IB:
And here's what it looks like running, when the iPad is in both orientations:
Voila! Custom size class configurations at runtime.
Hopefully Apple will make this unnecessary in the next version of the OS. In the meantime, this may be a more elegant and scalable approach than programmatically messing with auto-layout constraints or doing other manipulations in code.
~
EDIT (6/4/15): Please bear in mind that the sample code above is essentially a proof of concept to demonstrate the technique. Feel free to adapt as needed for your own specific application.
~
EDIT (7/24/15): It's gratifying that the above explanation seems to help demystify the issue. While I haven't tested it, the code by mohamede1945 [below] looks like a helpful optimization for practical purposes. Feel free to test it out and let us know what you think. (In the interest of completeness, I'll leave the sample code above as-is.)
As a summary to the very long answer by RonDiamond. All you need to do is in your root view controller.
Objective-c
- (UITraitCollection *)overrideTraitCollectionForChildViewController:(UIViewController *)childViewController
{
if (CGRectGetWidth(self.view.bounds) < CGRectGetHeight(self.view.bounds)) {
return [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassCompact];
} else {
return [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassRegular];
}
}
Swift:
override func overrideTraitCollectionForChildViewController(childViewController: UIViewController) -> UITraitCollection! {
if view.bounds.width < view.bounds.height {
return UITraitCollection(horizontalSizeClass: .Compact)
} else {
return UITraitCollection(horizontalSizeClass: .Regular)
}
}
Then in storyborad use compact width for Portrait and Regular width for Landscape.
The long and helpful answer by RonDiamond is a good start to comprehend the principles, however the code that worked for me (iOS 8+) is based on overriding method (UITraitCollection *)traitCollection
So, add constraints in InterfaceBuilder with variations for Width - Compact, for example for constraint's property Installed. So Width - Any will be valid for landscape, Width - Compact for Portrait.
To switch constraints in the code based on current view controller size, just add the following into your UIViewController class:
- (UITraitCollection *)traitCollection
{
UITraitCollection *verticalRegular = [UITraitCollection traitCollectionWithVerticalSizeClass:UIUserInterfaceSizeClassRegular];
if (self.view.bounds.size.width < self.view.bounds.size.height) {
// wCompact, hRegular
return [UITraitCollection traitCollectionWithTraitsFromCollections:
#[[UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassCompact],
verticalRegular]];
} else {
// wRegular, hRegular
return [UITraitCollection traitCollectionWithTraitsFromCollections:
#[[UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassRegular],
verticalRegular]];
}
}
The iPad has the 'regular' size trait for both horizontal and vertical dimensions, giving no distinction between portrait and landscape.
These size traits can be overridden in your custom UIViewController subclass code, via method traitCollection, for example:
- (UITraitCollection *)traitCollection {
// Distinguish portrait and landscape size traits for iPad, similar to iPhone 7 Plus.
// Be aware that `traitCollection` documentation advises against overriding it.
UITraitCollection *superTraits = [super traitCollection];
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
UITraitCollection *horizontalRegular = [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassRegular];
UITraitCollection *verticalRegular = [UITraitCollection traitCollectionWithVerticalSizeClass:UIUserInterfaceSizeClassRegular];
UITraitCollection *regular = [UITraitCollection traitCollectionWithTraitsFromCollections:#[horizontalRegular, verticalRegular]];
if ([superTraits containsTraitsInCollection:regular]) {
if (UIInterfaceOrientationIsPortrait([[UIApplication sharedApplication] statusBarOrientation])) {
// iPad in portrait orientation
UITraitCollection *horizontalCompact = [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassCompact];
return [UITraitCollection traitCollectionWithTraitsFromCollections:#[superTraits, horizontalCompact, verticalRegular]];
} else {
// iPad in landscape orientation
UITraitCollection *verticalCompact = [UITraitCollection traitCollectionWithVerticalSizeClass:UIUserInterfaceSizeClassCompact];
return [UITraitCollection traitCollectionWithTraitsFromCollections:#[superTraits, horizontalRegular, verticalCompact]];
}
}
}
return superTraits;
}
- (BOOL)prefersStatusBarHidden {
// Override to negate this documented special case, and avoid erratic hiding of status bar in conjunction with `traitCollection` override:
// For apps linked against iOS 8 or later, this method returns true if the view controller is in a vertically compact environment.
return NO;
}
This gives the iPad the same size traits as the iPhone 7 Plus. Note that other iPhone models generally have the 'compact width' trait (rather than regular width) regardless of orientation.
Mimicking the iPhone 7 Plus in this way allows that model to be used as a stand-in for the iPad in Xcode's Interface Builder, which is unaware of customizations in code.
Be aware that Split View on the iPad may use different size traits from normal full screen operation.
This answer is based on the approach taken in this blog post, with some improvements.
Update 2019-01-02: Updated to fix intermittent hidden status bar in iPad landscape, and potential trampling of (newer) traits in UITraitCollection. Also noted that Apple documentation actually recommends against overriding traitCollection, so in future there may turn out to be issues with this technique.
How much different is your landscape mode going to be than your portrait mode? If its very different, it might be a good idea to create another view controller and load it when the device is in landscape
For example
if (UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation))
//load landscape view controller here
Swift 5 Version. It works fine.
override func overrideTraitCollection(forChild childViewController: UIViewController) -> UITraitCollection? {
if UIScreen.main.bounds.width > UIScreen.main.bounds.height {
let collections = [UITraitCollection(horizontalSizeClass: .regular),
UITraitCollection(verticalSizeClass: .compact)]
return UITraitCollection(traitsFrom: collections)
}
return super.overrideTraitCollection(forChild: childViewController)
}
Swift 3.0 code for #RonDiamond solution
class Test : UIViewController {
var _willTransitionToPortrait: Bool?
var _traitCollection_CompactRegular: UITraitCollection?
var _traitCollection_AnyAny: UITraitCollection?
func viewDidLoad() {
super.viewDidLoad()
self.upReferenceSizeClasses = null
}
func setUpReferenceSizeClasses() {
var traitCollection_hCompact: UITraitCollection = UITraitCollection(horizontalSizeClass: UIUserInterfaceSizeClassCompact)
var traitCollection_vRegular: UITraitCollection = UITraitCollection(verticalSizeClass: UIUserInterfaceSizeClassRegular)
_traitCollection_CompactRegular = UITraitCollection(traitsFromCollections: [traitCollection_hCompact,traitCollection_vRegular])
var traitCollection_hAny: UITraitCollection = UITraitCollection(horizontalSizeClass: UIUserInterfaceSizeClassUnspecified)
var traitCollection_vAny: UITraitCollection = UITraitCollection(verticalSizeClass: UIUserInterfaceSizeClassUnspecified)
_traitCollection_AnyAny = UITraitCollection(traitsFromCollections: [traitCollection_hAny,traitCollection_vAny])
}
func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
_willTransitionToPortrait = self.view.frame.size.height > self.view.frame.size.width
}
func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
_willTransitionToPortrait = size.height > size.width
}
func overrideTraitCollectionForChildViewController(childViewController: UIViewController) -> UITraitCollection {
var traitCollectionForOverride: UITraitCollection = _willTransitionToPortrait ? _traitCollection_CompactRegular : _traitCollection_AnyAny
return traitCollectionForOverride
}}
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 am trying to use a UIScrollView as a navigation device in my iOS application. Basically, I want the application to load specific content, based on the position of a paging-enabled UIScrollView. It is sort of like a navigation wheel.
Currently, I have a two-page (2*320) scrollview, and I am using the scrollViewDidEndDragging delegate method and contentoffset.x to load the appropriate content. I try to determine the position of the scrollview as such:
if (scrollView.contentOffset.x < 320) {
// Load content 1
}
else if (scrollView.contentOffset.x >= 320) {
// Load content 2
}
My problem is that I either do not understand how to work with contentoffset or it is not suitable to deal with my problem. Either way, I am stuck. Can someone let me know where I went wrong and/or show me a more efficient way to determine scrollview position?
I am stupid, I forgot to mention what the problem is... Basically, I cannot track the position of the scrollview. When I move to the second page it only registers changes if I go over the 620 limit (bounce rightward). But when I go back to the starting position (page 1), it does register the changes. In other words, my tracking is not accurate.
Delegate and everything else is working fine, I log them!
It's hard to say because you never specifically mentioned what the problem is, but I'll take a couple guesses at it.
If the delegate method isn't being called at all you need to remember to set the scroll views delegate:
[myScrollView setDelegate:self];
Otherwise, the problem with using scrollViewDidEndDragging to detect the scroll views offset is that it will check the offset at the point where you stop dragging which could be within the destination pages rect, or within the starting pages rect. As an alternative, I'd suggest you use scrollViewDidEndDecelerating to check the content offset as it will be called as soon as the scroll view comes to a stop at its destination page.
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
if (scrollView.contentOffset.x < 320) {
NSLog(#"%#",NSStringFromCGPoint(scrollView.contentOffset));
}
else if (scrollView.contentOffset.x >= 320) {
NSLog(#"%#",NSStringFromCGPoint(scrollView.contentOffset));
}
}
I think this could help you :). With scrollViewDidScroll you always get the exact position of your scrollView:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView.contentOffset.x < 320)
{
// Load content 1
}
else if (scrollView.contentOffset.x >= 320)
{
// Load content 2
}
}
NSLog(#"Position: %g", scrollView.contentOffset.x);
Do not forget the delegate UIScrollViewDelegate in your .h file.
Here a solution for getting the current position (page) as integer Value:
Use scrollViewDidEndDecelerating: methode:
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
NSLog(#"%i", (int)(_scrollView.contentOffset.x / _scrollView.frame.size.width));
}
If for any reason, you'd like to be notified before the view ended decelerating; for example, to perform animations. You can also use this:
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
// Where the scroll will end (vertically)
let offSetY = targetContentOffset.pointee.y
// I've got a scroll view with paging enabled so I use this to check which "page" is selected.
if offSetY == firstPageView.frame.origin.y {
// Do something…
} else if offSetY == secondPageView.frame.origin.y {
// Do something…
}
}
Using UIScrollViewDelegate,
func scrollViewWillBeginDragging(scrollView: UIScrollView){
print("dragging begins")
print("Position: \(scrollView.contentOffset.x) , \(scrollView.contentOffset.y) ")
}
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)
}