MapView to show all annotations and zoom in as much as possible of the map - swift

I basically have a map where I add two annotations and they are shown as expected.
Then I´m using the following code:
let annotations = [start, end]
mapView?.showAnnotations(annotations, animated: false)
And this zooms to show my map with the two annotations, but the map could be zoomed in much more and show a more detailed map view.
This is what I get today and this is the expected result that I want.
As you can see the map could be zoomed in a lot more.
Any ideas how to achieve this?

Swift 5.0 / 4.2 version of nyxee's answer
extension MKMapView {
/// When we call this function, we have already added the annotations to the map, and just want all of them to be displayed.
func fitAll() {
var zoomRect = MKMapRect.null;
for annotation in annotations {
let annotationPoint = MKMapPoint(annotation.coordinate)
let pointRect = MKMapRect(x: annotationPoint.x, y: annotationPoint.y, width: 0.01, height: 0.01);
zoomRect = zoomRect.union(pointRect);
}
setVisibleMapRect(zoomRect, edgePadding: UIEdgeInsets(top: 100, left: 100, bottom: 100, right: 100), animated: true)
}
/// We call this function and give it the annotations we want added to the map. we display the annotations if necessary
func fitAll(in annotations: [MKAnnotation], andShow show: Bool) {
var zoomRect:MKMapRect = MKMapRect.null
for annotation in annotations {
let aPoint = MKMapPoint(annotation.coordinate)
let rect = MKMapRect(x: aPoint.x, y: aPoint.y, width: 0.1, height: 0.1)
if zoomRect.isNull {
zoomRect = rect
} else {
zoomRect = zoomRect.union(rect)
}
}
if(show) {
addAnnotations(annotations)
}
setVisibleMapRect(zoomRect, edgePadding: UIEdgeInsets(top: 100, left: 100, bottom: 100, right: 100), animated: true)
}
}

Use this:
extension MKMapView {
/// when we call this function, we have already added the annotations to the map, and just want all of them to be displayed.
func fitAll() {
var zoomRect = MKMapRectNull;
for annotation in annotations {
let annotationPoint = MKMapPointForCoordinate(annotation.coordinate)
let pointRect = MKMapRectMake(annotationPoint.x, annotationPoint.y, 0.01, 0.01);
zoomRect = MKMapRectUnion(zoomRect, pointRect);
}
setVisibleMapRect(zoomRect, edgePadding: UIEdgeInsetsMake(100, 100, 100, 100), animated: true)
}
/// we call this function and give it the annotations we want added to the map. we display the annotations if necessary
func fitAll(in annotations: [MKAnnotation], andShow show: Bool) {
var zoomRect:MKMapRect = MKMapRectNull
for annotation in annotations {
let aPoint = MKMapPointForCoordinate(annotation.coordinate)
let rect = MKMapRectMake(aPoint.x, aPoint.y, 0.1, 0.1)
if MKMapRectIsNull(zoomRect) {
zoomRect = rect
} else {
zoomRect = MKMapRectUnion(zoomRect, rect)
}
}
if(show) {
addAnnotations(annotations)
}
setVisibleMapRect(zoomRect, edgePadding: UIEdgeInsets(top: 100, left: 100, bottom: 100, right: 100), animated: true)
}
}
then, after creating an outlet for your MapView, for example as map, and with you annotations either added to the map of in an array called annotations, access the above methods like so:
map.fitAll()
OR
map.fitAll(in:annotations, true)
respectively.
Use true or false in the last statement depending on whether you had added the annotations to the map before or not...

In Swift:
self.mapView.showAnnotations(self.mapView.annotations, animated: true)
This is what I use to show all annotations. almost the same as you are doing yes. the map zoom this to fit the visible or usable section of the map, on my app the zoom is good and does add some padding so that none of the pins are touching the edges.
so possibly once the maps is done you can quickly use setVisibleMapRect:edgePadding:animated: using the new visibleRect with a negative padding.

Swift 5.3 version with adjustable padding.
extension MKMapView {
func fitAllAnnotations(with padding: UIEdgeInsets = UIEdgeInsets(top: 100, left: 100, bottom: 100, right: 100)) {
var zoomRect: MKMapRect = .null
annotations.forEach({
let annotationPoint = MKMapPoint($0.coordinate)
let pointRect = MKMapRect(x: annotationPoint.x, y: annotationPoint.y, width: 0.01, height: 0.01)
zoomRect = zoomRect.union(pointRect)
})
setVisibleMapRect(zoomRect, edgePadding: padding, animated: true)
}
func fit(annotations: [MKAnnotation], andShow show: Bool, with padding: UIEdgeInsets = UIEdgeInsets(top: 100, left: 100, bottom: 100, right: 100)) {
var zoomRect: MKMapRect = .null
annotations.forEach({
let aPoint = MKMapPoint($0.coordinate)
let rect = MKMapRect(x: aPoint.x, y: aPoint.y, width: 0.1, height: 0.1)
zoomRect = zoomRect.isNull ? rect : zoomRect.union(rect)
})
if show {
addAnnotations(annotations)
}
setVisibleMapRect(zoomRect, edgePadding: padding, animated: true)
}
}

Related

contentInset take only negative number

I'm trying to align vertically a text in a view.
For this, I call contentInset function, like this:
override func alignCenterVertical() {
let fittingSize = CGSize(width: bounds.width, height: CGFloat.greatestFiniteMagnitude)
let size = sizeThatFits(fittingSize)
let topOffset = (bounds.size.height - size.height * zoomScale) / 2
let positiveTopOffset = max(1, topOffset)
contentInset = UIEdgeInsets(top: positiveTopOffset, left: 0, bottom: 0, right: 0)
}
But this only work when the value "positiveTopOffset" is negative. (reverse that I want)
I want exactly this comportment but with "positiveTopOffset" positive, like in this part of code here.
When I let the code like this, with "positiveTopOffset" positive, nothing append. Why?
thanks a lot!
I finally found the problem.
I have to add isScrollingEnable = true, like this:
override func alignCenterVertical() {
let fittingSize = CGSize(width: bounds.width, height: CGFloat.greatestFiniteMagnitude)
isScrollingEnabled = true
let size = sizeThatFits(fittingSize)
let topOffset = (bounds.size.height - size.height * zoomScale) / 2
let positiveTopOffset = max(1, topOffset)
contentInset = UIEdgeInsets(top: positiveTopOffset, left: 0, bottom: 0, right: 0)
}

Drawing a dotted background in swift

Im trying to achieve a dotted view that I can use as a background. Something looking a bit like this:
But I seem to be missing something in my code. I have tried to lab around with different sizes of the dots and everything, but so far I'm just getting my background color set to the view but no dots no matter the size, the color or the spacing. What am I missing?
class DottedBackgroundView: UIView {
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
UIColor.green.setFill()
context.fill(rect)
let drawPattern: CGPatternDrawPatternCallback = { _, context in
context.addArc(
center: CGPoint(x: 5, y: 5), radius: 2.5,
startAngle: 0, endAngle: CGFloat(2.0 * .pi),
clockwise: false)
context.setFillColor(UIColor.white.cgColor)
context.fillPath()
}
var callbacks = CGPatternCallbacks(
version: 0, drawPattern: drawPattern, releaseInfo: nil)
let pattern = CGPattern(
info: nil,
bounds: CGRect(x: 0, y: 0, width: 10, height: 10),
matrix: .identity,
xStep: 10,
yStep: 10,
tiling: .constantSpacing,
isColored: true,
callbacks: &callbacks)
let patternSpace = CGColorSpace(patternBaseSpace: nil)!
context.setFillColorSpace(patternSpace)
var alpha: CGFloat = 1.0
context.setFillPattern(pattern!, colorComponents: &alpha)
context.fill(rect)
context.addArc(
center: CGPoint(x: 5, y: 5), radius: 5.0,
startAngle: 0, endAngle: CGFloat(2.0 * .pi),
clockwise: false)
}
}
You have a bug. Just change second line to UIColor.white.setFill() and inside drawPattern change color to black: context.setFillColor(UIColor.black.cgColor).
It works. I set this view on Storyboard, but if you add it from code try this:
let dottedView = DottedBackgroundView()
dottedView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(dottedView)
NSLayoutConstraint.activate([
dottedView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
dottedView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
dottedView.topAnchor.constraint(equalTo: self.view.topAnchor),
dottedView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor)
])
You need to set constraints, because your custom view doesn't have defined size, therefore it's not able to resize properly.

Swift - How to make animated arrow point in right direction?

I have two buttons which when connected, show a line with an animated arrow to show that they are connected.The issue is the arrow it self is not rotating appropriately to point in the right direction when animated.
How can I rotate the arrow to point in the right direction when moving?
class ViewController: UIViewController {
let line = CAShapeLayer()
let linePath = UIBezierPath()
var triangleImage = UIImage(named: "triangle" )
let startCoordinate = CGRect(x: 100, y: 100, width: 0, height: 0)
let btn1 = UIButton()
let btn2 = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
btn1.createRectangleButton(buttonPositionX: 100, buttonPositionY: 100, buttonWidth: 80, buttonHeight: 40, buttonTitle: "", buttonTag: 0)
btn2.createRectangleButton(buttonPositionX: 300, buttonPositionY: 400, buttonWidth: 80, buttonHeight: 40, buttonTitle: "", buttonTag: 1)
view.addSubview(btn1)
view.addSubview(btn2)
let imageView = UIImageView(image: triangleImage)
imageView.frame = CGRect(x:100, y:100, width: 20, height: 20)
imageView.center = self.btn1.center
view.addSubview(imageView)
linePath.move(to: btn1.center)
linePath.addLine(to: btn2.center)
line.path = linePath.cgPath
line.strokeColor = UIColor.red.cgColor
self.view.layer.addSublayer(line)
view.bringSubviewToFront(imageView)
UIView.animate(withDuration: 3, delay: 0.0, options: [.repeat, .curveLinear], animations: {
imageView.center = self.btn2.center
}, completion: nil)
}
note: the code above will look slightly different to the image
Try this rotation transformation
// rotation
imageView.transform = CGAffineTransform(rotationAngle: atan2(btn2.center.y - btn1.center.y, btn2.center.x - btn1.center.x) + CGFloat.pi / 2)
// used triangle image below

'MKMapRectIsNull' has been replaced by property 'MKMapRect.isNull'

So in updating to Xcode 10 and Swift 4.2, of course I had to make a lot of changes in my project to update the syntax. I was able to rectify all issues except for one. I'm getting an error that says: 'MKMapRectIsNull' has been replaced by property 'MKMapRect.isNull'. I did the obvious thing of trying replacing MKMapRectIsNull with MKMapRect.isNull, but that generates another error which says: Instance member 'isNull' cannot be used on type 'MKMapRect'. Here's some more context:
var zoomRect = MKMapRect.null
for annotation in map.annotations {
let annotationPoint = MKMapPoint(annotation.coordinate)
let pointRect = MKMapRect(x: annotationPoint.x, y: annotationPoint.y, width: 0, height: 0)
if (MKMapRect.isNull(zoomRect)) {
zoomRect = pointRect
} else {
zoomRect = zoomRect.union(pointRect)
}
}
map.setVisibleMapRect(zoomRect, edgePadding: UIEdgeInsets(top: 40, left: 40, bottom: 40, right: 40), animated: true)
Any ideas/help would be appreciated.
For the condition if (MKMapRect.isNull(zoomRect)) you need to change that to if (zoomRect.isNull)
You cannot check the condition for MKMapRect type, only an instantiated object of that type.

UIButton in Swift with two images

I'm trying to make a UIButton that has a UIImage on either side (left and right) of the button's title. Ideally, the images would be pinned to the left and right sides of the button, and the title would be centered, but I can live with the images being right next to the title label I suppose. I have been able to add one image, which appears right before the title, but how would I add a second?
I know this is an old question but since no code was provided and someone asked for it I figured I would share my solution for this.
What I did was create a new class which subclasses a UIButton. I made sure that you can see all the changes you do instantly in your interface builder as well. So you can just drag in a UIButton. Specify the class to your own class and it will allow you to set the images and see it being drawn instantly
Here is what I did to achieve this
import UIKit
#IBDesignable
class AddProfilePictureView: UIButton {
#IBInspectable var leftHandImage: UIImage? {
didSet {
leftHandImage = leftHandImage?.withRenderingMode(.alwaysOriginal)
setupImages()
}
}
#IBInspectable var rightHandImage: UIImage? {
didSet {
rightHandImage = rightHandImage?.withRenderingMode(.alwaysTemplate)
setupImages()
}
}
func setupImages() {
if let leftImage = leftHandImage {
self.setImage(leftImage, for: .normal)
self.imageView?.contentMode = .scaleAspectFill
self.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: self.frame.width - (self.imageView?.frame.width)!)
}
if let rightImage = rightHandImage {
let rightImageView = UIImageView(image: rightImage)
rightImageView.tintColor = COLOR_BLUE
let height = self.frame.height * 0.2
let width = height
let xPos = self.frame.width - width
let yPos = (self.frame.height - height) / 2
rightImageView.frame = CGRect(x: xPos, y: yPos, width: width, height: height)
self.addSubview(rightImageView)
}
}
}
I would break it into two buttons within a collection view- it's very flexible and can work great for something like this.
Add a UIImageView with an image to the button by addSubview and another to the button by setImage, it can be adjusted with titleEdgeInsets and imageEdgeInsets.
if you need a code sample – let me know.
Hope it helps!
Swift 5, XCode 11.6
Please use the below code in your extension or in a custom UIButton class. I have used the below code in my custom class. The function will set left or right image respectively and align the text.
/// Sets a left and right image for a button
/// - Parameters:
/// - right: right image
/// - left: left image
func setImages(right: UIImage? = nil, left: UIImage? = nil) {
if let leftImage = left, right == nil {
setImage(leftImage, for: .normal)
imageEdgeInsets = UIEdgeInsets(top: 5, left: (bounds.width - 35), bottom: 5, right: 5)
titleEdgeInsets = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: (imageView?.frame.width)!)
contentHorizontalAlignment = .left
}
if let rightImage = right, left == nil {
setImage(rightImage, for: .normal)
imageEdgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: (bounds.width - 35))
titleEdgeInsets = UIEdgeInsets(top: 0, left: (imageView?.frame.width)!, bottom: 0, right: 10)
contentHorizontalAlignment = .right
}
if let rightImage = right, let leftImage = left {
setImage(rightImage, for: .normal)
imageEdgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: (bounds.width - 35))
titleEdgeInsets = UIEdgeInsets(top: 0, left: (imageView?.frame.width)!, bottom: 0, right: 10)
contentHorizontalAlignment = .left
let leftImageView = UIImageView(frame: CGRect(x: bounds.maxX - 30,
y: (titleLabel?.bounds.midY)! - 5,
width: 20,
height: frame.height - 10))
leftImageView.image?.withRenderingMode(.alwaysOriginal)
leftImageView.image = leftImage
leftImageView.contentMode = .scaleAspectFit
leftImageView.layer.masksToBounds = true
addSubview(leftImageView)
}
}
Usage:
vegaButton.setImage(right: myImage) // for right image
vegaButton.setImage(left: myImage) // for left image
vegaButton.setImage(right: myImage, left: myImage) // for both images.