Problem
How can I modify the scroll target of a scrollView? I am looking for kind of a replacement for the "classic" scrollView delegate method
override func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>)
...where we can modfify the targeted scrollView.contentOffset via targetContentOffset.pointee for instance to create a custom paging behaviour.
Or in other words: I do want to create a paging effect in a (horizontal) scrollView.
What I have tried ie. is something like this:
ScrollView(.horizontal, showsIndicators: true, content: {
HStack(alignment: VerticalAlignment.top, spacing: 0, content: {
card(title: "1")
card(title: "2")
card(title: "3")
card(title: "4")
})
})
// 3.
.content.offset(x: self.dragState.isDragging == true ? self.originalOffset : self.modifiedOffset, y: 0)
// 4.
.animation(self.dragState.isDragging == true ? nil : Animation.spring())
// 5.
.gesture(horizontalDragGest)
Attempt
This is what I tried (besides a custom scrollView approach):
A scrollView has a content area larger then screen space to enable scrolling at all.
I created a DragGesture() to detect if there is a drag going on. In the .onChanged and .onEnded closures I modified my #State values to create a desired scrollTarget.
Conditionally fed in both the original unchanged and the new modified values into the .content.offset(x: y:) modifier - depending on the dragState as a replacement for missing scrollDelegate methods.
Added animation acting conditionally only when drag has ended.
Attached the gesture to the scrollView.
Long story short. It doesn't work.
I hope I got across what my problem is.
Any solutions out there? Looking forward to any input. Thanks!
I have managed to achieve a paging behaviour with a #Binding index. The solution might look dirty, I'll explain my workarounds.
The first thing I got wrong, was to get alignment to .leading instead of the default .center, otherwise the offset works unusual. Then I combined the binding and a local offset state. This kinda goes against the "Single source of truth" principle, but otherwise I had no idea how to handle external index changes and modify my offset.
So, my code is the following
struct SwiftUIPagerView<Content: View & Identifiable>: View {
#Binding var index: Int
#State private var offset: CGFloat = 0
#State private var isGestureActive: Bool = false
// 1
var pages: [Content]
var body: some View {
GeometryReader { geometry in
ScrollView(.horizontal, showsIndicators: false) {
HStack(alignment: .center, spacing: 0) {
ForEach(self.pages) { page in
page
.frame(width: geometry.size.width, height: nil)
}
}
}
// 2
.content.offset(x: self.isGestureActive ? self.offset : -geometry.size.width * CGFloat(self.index))
// 3
.frame(width: geometry.size.width, height: nil, alignment: .leading)
.gesture(DragGesture().onChanged({ value in
// 4
self.isGestureActive = true
// 5
self.offset = value.translation.width + -geometry.size.width * CGFloat(self.index)
}).onEnded({ value in
if -value.predictedEndTranslation.width > geometry.size.width / 2, self.index < self.pages.endIndex - 1 {
self.index += 1
}
if value.predictedEndTranslation.width > geometry.size.width / 2, self.index > 0 {
self.index -= 1
}
// 6
withAnimation { self.offset = -geometry.size.width * CGFloat(self.index) }
// 7
DispatchQueue.main.async { self.isGestureActive = false }
}))
}
}
}
you may just wrap your content, I used it for "Tutorial Views".
this a trick to switch between external and internal state changes
.leading is mandatory if you don't want to translate all offsets to center.
set the state to local state change
calculate the full offset from the gesture delta (*-1) plus the previous index state
at the end set the final index based on the gesture predicted end, while rounding the offset up or down
reset the state to handle external changes to index
I have tested it in the following context
struct WrapperView: View {
#State var index: Int = 0
var body: some View {
VStack {
SwiftUIPagerView(index: $index, pages: (0..<4).map { index in TODOView(extraInfo: "\(index + 1)") })
Picker(selection: self.$index.animation(.easeInOut), label: Text("")) {
ForEach(0..<4) { page in Text("\(page + 1)").tag(page) }
}
.pickerStyle(SegmentedPickerStyle())
.padding()
}
}
}
where TODOView is my custom view that indicates a view to implement.
I hope I get the question right, if not please specify which part should I focus on. Also I welcome any suggestions to remove the isGestureActive state.
#gujci your solution is perfect, for more general usage, make it accept Models and view builder as in (note the I pass the geometry size in the builder) :
struct SwiftUIPagerView<TModel: Identifiable ,TView: View >: View {
#Binding var index: Int
#State private var offset: CGFloat = 0
#State private var isGestureActive: Bool = false
// 1
var pages: [TModel]
var builder : (CGSize, TModel) -> TView
var body: some View {
GeometryReader { geometry in
ScrollView(.horizontal, showsIndicators: false) {
HStack(alignment: .center, spacing: 0) {
ForEach(self.pages) { page in
self.builder(geometry.size, page)
}
}
}
// 2
.content.offset(x: self.isGestureActive ? self.offset : -geometry.size.width * CGFloat(self.index))
// 3
.frame(width: geometry.size.width, height: nil, alignment: .leading)
.gesture(DragGesture().onChanged({ value in
// 4
self.isGestureActive = true
// 5
self.offset = value.translation.width + -geometry.size.width * CGFloat(self.index)
}).onEnded({ value in
if -value.predictedEndTranslation.width > geometry.size.width / 2, self.index < self.pages.endIndex - 1 {
self.index += 1
}
if value.predictedEndTranslation.width > geometry.size.width / 2, self.index > 0 {
self.index -= 1
}
// 6
withAnimation { self.offset = -geometry.size.width * CGFloat(self.index) }
// 7
DispatchQueue.main.async { self.isGestureActive = false }
}))
}
}
}
and can be used as :
struct WrapperView: View {
#State var index: Int = 0
#State var items : [(color:Color,name:String)] = [
(.red,"Red"),
(.green,"Green"),
(.yellow,"Yellow"),
(.blue,"Blue")
]
var body: some View {
VStack(spacing: 0) {
SwiftUIPagerView(index: $index, pages: self.items.identify { $0.name }) { size, item in
TODOView(extraInfo: item.model.name)
.frame(width: size.width, height: size.height)
.background(item.model.color)
}
Picker(selection: self.$index.animation(.easeInOut), label: Text("")) {
ForEach(0..<4) { page in Text("\(page + 1)").tag(page) }
}
.pickerStyle(SegmentedPickerStyle())
}.edgesIgnoringSafeArea(.all)
}
}
with the help of some utilities :
struct MakeIdentifiable<TModel,TID:Hashable> : Identifiable {
var id : TID {
return idetifier(model)
}
let model : TModel
let idetifier : (TModel) -> TID
}
extension Array {
func identify<TID: Hashable>(by: #escaping (Element)->TID) -> [MakeIdentifiable<Element, TID>]
{
return self.map { MakeIdentifiable.init(model: $0, idetifier: by) }
}
}
#gujci, thank you for interesting example. I've played with it and removed the isGestureActive state. Full example may be found in my gist.
struct SwiftUIPagerView<Content: View & Identifiable>: View {
#State private var index: Int = 0
#State private var offset: CGFloat = 0
var pages: [Content]
var body: some View {
GeometryReader { geometry in
ScrollView(.horizontal, showsIndicators: false) {
HStack(alignment: .center, spacing: 0) {
ForEach(self.pages) { page in
page
.frame(width: geometry.size.width, height: nil)
}
}
}
.content.offset(x: self.offset)
.frame(width: geometry.size.width, height: nil, alignment: .leading)
.gesture(DragGesture()
.onChanged({ value in
self.offset = value.translation.width - geometry.size.width * CGFloat(self.index)
})
.onEnded({ value in
if abs(value.predictedEndTranslation.width) >= geometry.size.width / 2 {
var nextIndex: Int = (value.predictedEndTranslation.width < 0) ? 1 : -1
nextIndex += self.index
self.index = nextIndex.keepIndexInRange(min: 0, max: self.pages.endIndex - 1)
}
withAnimation { self.offset = -geometry.size.width * CGFloat(self.index) }
})
)
}
}
}
As far as I know scrolls in swiftUI doesn't support anything potentially useful such as scrollViewDidScroll or scrollViewWillEndDragging yet. I suggest using either classic UIKit views for making very custom behavior and cool SwiftUI views for anything that is easier. I've tried that a lot and it actually works! Have a look at this guide. Hope that helps
Alternative solution would be to integrate UIKit into SwiftUI using UIViewRepresentative which links UIKit components with SwiftUI. For additional leads and resources, see how Apple suggests you interface with UIKit: Interfacing with UIKit. They have a good example that shows to page between images and track selection index.
Edit: Until they (Apple) implement some sort of content offset that effects the scroll instead of the entire view, this is their suggested solution since they knew the initial release of SwiftUI wouldn't encompass all functionality of UIKit.
Details
Xcode 14
Swift 5.6.1
Requirements
I do not want to use integration with UIKit (clean SwiftUI ONLY)
I do not want to scroll to the any "ID", I want to scroll to the point
Solution
import SwiftUI
#available(iOS 14.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
struct ExtendedScrollView<Content>: View where Content: View {
private let contentProvider: _AligningContentProvider<Content>
// Main Idea from: https://github.com/edudnyk/SolidScroll/blob/main/Sources/SolidScroll/ScrollView.swift
private var config: _ScrollViewConfig
init(config: _ScrollViewConfig = _ScrollViewConfig(),
#ViewBuilder content: () -> Content) {
contentProvider = _AligningContentProvider(content: content(), horizontal: .center, vertical: .center)
self.config = config
}
init(_ axes: Axis.Set = .vertical,
showsIndicators: Bool = true,
#ViewBuilder content: () -> Content) {
var config = _ScrollViewConfig()
config.showsHorizontalIndicator = axes.contains(.horizontal) && showsIndicators
config.showsVerticalIndicator = axes.contains(.vertical) && showsIndicators
self.init(config: config, content: content)
}
init(config: () -> _ScrollViewConfig,
#ViewBuilder content: () -> Content) {
self.init(config: config(), content: content)
}
var body: some View {
_ScrollView(contentProvider: contentProvider, config: config)
}
}
extension _ContainedScrollViewKey: PreferenceKey {}
// MARK: Track ScrollView Scrolling
struct TrackableExtendedScrollView: ViewModifier {
let onChange: (_ScrollViewProxy?) -> Void
func body(content: Content) -> some View {
content
.onPreferenceChange(_ContainedScrollViewKey.self, perform: onChange)
}
}
extension View {
func onScrollChange(perform: #escaping (_ScrollViewProxy?) -> Void) -> some View {
modifier(TrackableExtendedScrollView(onChange: perform))
}
}
Usage Sample
private var gridItemLayout = (0..<40).map { _ in
GridItem(.fixed(50), spacing: 0, alignment: .leading)
}
// ....
ExtendedScrollView() {
LazyHGrid(rows: gridItemLayout) {
ForEach((0..<numberOfRows*numberOfColumns), id: \.self) { index in
let color = (index/numberOfRows)%2 == 0 ? Color(0x94D2BD) : Color(0xE9D8A6)
Text("\(index)")
.frame(width: 50)
.frame(maxHeight: .infinity)
}
}
}
.onScrollChange { proxy in
// let offset = proxy?.contentOffset.y
}
Full Sample
Implementation details
First column and first row are always on the screen
There are 3 "CollectionView":
first row "CollectionView"
first column "CollectionView"
main content "CollectionView"
All "CollectionView" are synced (if you scroll one "CollectionView", another will also be scrolled)
Do not forget to paste The Solution code here
import SwiftUI
import Combine
struct ContentView: View {
private let columWidth: CGFloat = 50
private var gridItemLayout0 = [GridItem(.fixed(50), spacing: 0, alignment: .leading)]
private var gridItemLayout1 = [GridItem(.fixed(50), spacing: 0, alignment: .leading)]
private var gridItemLayout = (0..<40).map { _ in
GridItem(.fixed(50), spacing: 0, alignment: .leading)
}
#State var text: String = "scrolling not detected"
#State private var scrollViewProxy1: _ScrollViewProxy?
#State private var tableContentScrollViewProxy: _ScrollViewProxy?
#State private var tableHeaderScrollViewProxy: _ScrollViewProxy?
private let numberOfColumns = 50
private let numberOfRows = 40
let headerColor = Color(0xEE9B00)
let firstColumnColor = Color(0x0A9396)
let headerTextColor = Color(.white)
let horizontalSpacing: CGFloat = 6
let verticalSpacing: CGFloat = 0
let firstColumnWidth: CGFloat = 100
let columnWidth: CGFloat = 60
var body: some View {
VStack(spacing: 0) {
Text("First column and row are sticked to the content")
.foregroundColor(.gray)
Text(text)
HStack {
Rectangle()
.frame(width: firstColumnWidth-2)
.foregroundColor(.clear)
buildFirstCollectionViewRow()
}
.frame(height: 50)
HStack(alignment: .firstTextBaseline, spacing: horizontalSpacing) {
buildFirstCollectionViewColumn()
buildCollectionViewContent()
}
}
}
#ViewBuilder
private func buildFirstCollectionViewRow() -> some View {
ExtendedScrollView() {
LazyHGrid(rows: gridItemLayout1, spacing: horizontalSpacing) {
ForEach((0..<numberOfColumns), id: \.self) {
let color = $0%2 == 0 ? Color(0x005F73) : Color(0xCA6702)
Text("Value\($0)")
.frame(width: columnWidth)
.frame(maxHeight: .infinity)
.foregroundColor(headerTextColor)
.background(color)
.font(.system(size: 16, weight: .semibold))
}
}
}
.onScrollChange { proxy in
if tableHeaderScrollViewProxy != proxy { tableHeaderScrollViewProxy = proxy }
guard proxy?.isScrolling ?? false else { return }
if tableHeaderScrollViewProxy?.contentOffset.x != tableContentScrollViewProxy?.contentOffset.x,
let offset = proxy?.contentOffset.x {
tableContentScrollViewProxy?.contentOffset.x = offset
}
text = "scrolling: header"
}
}
}
// MARK: Collection View Elements
extension ContentView {
#ViewBuilder
private func buildFirstCollectionViewColumn() -> some View {
ExtendedScrollView() {
LazyHGrid(rows: gridItemLayout, spacing: horizontalSpacing) {
ForEach((0..<numberOfRows), id: \.self) {
Text("multi line text \($0)")
.foregroundColor(.white)
.lineLimit(2)
.frame(width: firstColumnWidth)
.font(.system(size: 16, weight: .semibold))
.frame(maxHeight: .infinity)
.background(firstColumnColor)
.border(.white)
}
}
}
.frame(width: firstColumnWidth)
.onScrollChange { proxy in
if scrollViewProxy1 != proxy { scrollViewProxy1 = proxy }
guard proxy?.isScrolling ?? false else { return }
if scrollViewProxy1?.contentOffset.y != tableContentScrollViewProxy?.contentOffset.y,
let offset = proxy?.contentOffset.y {
tableContentScrollViewProxy?.contentOffset.y = offset
}
text = "scrolling: 1st column"
}
}
#ViewBuilder
private func buildCollectionViewContent() -> some View {
ExtendedScrollView() {
LazyHGrid(rows: gridItemLayout, spacing: horizontalSpacing) {
ForEach((0..<numberOfRows*numberOfColumns), id: \.self) { index in
let color = (index/numberOfRows)%2 == 0 ? Color(0x94D2BD) : Color(0xE9D8A6)
Text("\(index)")
.frame(width: columnWidth)
.frame(maxHeight: .infinity)
.background(color)
.border(.white)
}
}
}
.onScrollChange { proxy in
if tableContentScrollViewProxy != proxy { tableContentScrollViewProxy = proxy }
guard proxy?.isScrolling ?? false else { return }
if scrollViewProxy1?.contentOffset.y != tableContentScrollViewProxy?.contentOffset.y,
let offset = proxy?.contentOffset.y {
self.scrollViewProxy1?.contentOffset.y = offset
}
if tableHeaderScrollViewProxy?.contentOffset.x != tableContentScrollViewProxy?.contentOffset.x,
let offset = proxy?.contentOffset.x {
self.tableHeaderScrollViewProxy?.contentOffset.x = offset
}
text = "scrolling: content"
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
extension Color {
init(_ hex: UInt, alpha: Double = 1) {
self.init(
.sRGB,
red: Double((hex >> 16) & 0xFF) / 255,
green: Double((hex >> 8) & 0xFF) / 255,
blue: Double(hex & 0xFF) / 255,
opacity: alpha
)
}
}
Full Sample Demo
I have a question regarding creating a Apple Map Overlay. I am trying to set on odd shape overlay from a JSON file. I have researched this on Stack Overflow, and have tried many of the solutions, but none seem to work. My code is below:
import UIKit
import MapKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate, UIGestureRecognizerDelegate {
#IBOutlet weak var mapView: MKMapView!
var coordinate: CLLocationCoordinate2D?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
mapView.showsUserLocation = true
mapView.delegate = self
mapView.mapType = .standard
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.TapGesture))
mapView.addGestureRecognizer(gestureRecognizer)
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if overlay is MKPolygon {
let polygonView = MKPolygonRenderer(overlay: overlay)
polygonView.strokeColor = UIColor.black
polygonView.lineWidth = 0.5
polygonView.fillColor = UIColor.blue
return polygonView
}
return MKOverlayRenderer()
}
#objc func TapGesture(gesRect: UITapGestureRecognizer) {
let location = gesRect.location(in: mapView)
coordinate = mapView.convert(location,toCoordinateFrom: mapView)
let locCoord = mapView.convert(location, toCoordinateFrom: mapView)
print("Tapped at lat: \(locCoord.latitude) long: \(locCoord.longitude)")
print("Tapped at: \(location)")
self.retreiveShape() { (full_shape) in
if let shape = full_shape {
let polygon = MKPolygon.init(coordinates: shape, count: shape.count)
self.mapView.addOverlay(polygon)
} else {
print("ARRAY EMPTY")
}
}
}
func retreiveShape(completion: #escaping ([CLLocationCoordinate2D]?) -> ()) {
let path = Bundle.main.path(forResource: "shape", ofType: "json")
var coord_array = [CLLocationCoordinate2D]()
do {
let data = try Data.init(contentsOf: URL.init(fileURLWithPath: path!))
let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
if let dictionary = json as? [String: Any] {
if let shape = dictionary["shape"] as? Array<Any> {
for regions in shape {
guard let region = regions as? Array<Array<Array<Double>>> else {
print("NOT HAPPENING")
return
}
for sections in region {
for coord in sections {
print("LATITUDE: \(coord[0])", "LONGITUDE: \(coord[1])")
let coordinatesToAppend = CLLocationCoordinate2D(latitude: coord[0], longitude: coord[1])
coord_array.append(coordinatesToAppend)
}
}
}
completion(coord_array)
}
}
} catch let error {
print(error)
}
}
The shape.json file is below:
{
"shape":[
[
[
[-81.621199, 30.282314],
[-81.613987, 30.281941],
[-81.611277, 30.284743],
[-81.602735, 30.284026],
[-81.601978, 30.292561],
[-81.596275, 30.290861],
[-81.592406, 30.290182],
[-81.571146, 30.28763],
[-81.55922, 30.286602],
[-81.559148, 30.291132],
[-81.558633, 30.294747],
[-81.55881, 30.312887],
[-81.558601, 30.312888],
[-81.558622, 30.316235],
[-81.558313, 30.316828],
[-81.552252, 30.320252],
[-81.548471, 30.321618],
[-81.527882, 30.323989],
[-81.529486, 30.328076],
[-81.537635, 30.336704],
[-81.537706, 30.337221],
[-81.538717, 30.338277],
[-81.539343, 30.338462],
[-81.542809, 30.341686],
[-81.547286, 30.345211],
[-81.552498, 30.348839],
[-81.552559, 30.352445],
[-81.577566, 30.352039],
[-81.578098, 30.353324],
[-81.578161, 30.35642],
[-81.577294, 30.3596],
[-81.576996, 30.366609],
[-81.58011, 30.366553],
[-81.580875, 30.37062],
[-81.580844, 30.373862],
[-81.581462, 30.374486],
[-81.578114, 30.374236],
[-81.572908, 30.374611],
[-81.562232, 30.372303],
[-81.551965, 30.366559],
[-81.548676, 30.365568],
[-81.540187, 30.378172],
[-81.538175, 30.380467],
[-81.538213, 30.387239],
[-81.536613, 30.388739],
[-81.512612, 30.392739],
[-81.505211, 30.390739],
[-81.490911, 30.392139],
[-81.49085, 30.389014],
[-81.489978, 30.389207],
[-81.488818, 30.38775],
[-81.489203, 30.389266],
[-81.487056, 30.390019],
[-81.481446, 30.391262],
[-81.479505, 30.39117],
[-81.477708, 30.390635],
[-81.476792, 30.390609],
[-81.476244, 30.391002],
[-81.473212, 30.389422],
[-81.472125, 30.388436],
[-81.472225, 30.388071],
[-81.474072, 30.386758],
[-81.475085, 30.384287],
[-81.474394, 30.381898],
[-81.473246, 30.38059],
[-81.473337, 30.380112],
[-81.47295, 30.379864],
[-81.472643, 30.380053],
[-81.471914, 30.379532],
[-81.471629, 30.378346],
[-81.470845, 30.377256],
[-81.468671, 30.376016],
[-81.466871, 30.374481],
[-81.465402, 30.374424],
[-81.464374, 30.373764],
[-81.465116, 30.373015],
[-81.467728, 30.372493],
[-81.469102, 30.371435],
[-81.470279, 30.369931],
[-81.472008, 30.370608],
[-81.473695, 30.370041],
[-81.471862, 30.370238],
[-81.470952, 30.369737],
[-81.471715, 30.369462],
[-81.470506, 30.369378],
[-81.469456, 30.368207],
[-81.468051, 30.367707],
[-81.46754, 30.366828],
[-81.466905, 30.366464],
[-81.467432, 30.366219],
[-81.466928, 30.365735],
[-81.465222, 30.365136],
[-81.464909, 30.364103],
[-81.46316, 30.362764],
[-81.463369, 30.36188],
[-81.462197, 30.361235],
[-81.461151, 30.36123],
[-81.46117, 30.360531],
[-81.461878, 30.360305],
[-81.461619, 30.359642],
[-81.461873, 30.358669],
[-81.461645, 30.358376],
[-81.460504, 30.358329],
[-81.46288, 30.357969],
[-81.462786, 30.357137],
[-81.461247, 30.355282],
[-81.460556, 30.352518],
[-81.46184, 30.340222],
[-81.462497, 30.339325],
[-81.465064, 30.337897],
[-81.471588, 30.328301],
[-81.472988, 30.318258],
[-81.469123, 30.319481],
[-81.450496, 30.320896],
[-81.443818, 30.302908],
[-81.442451, 30.301512],
[-81.438991, 30.299798],
[-81.437921, 30.298031],
[-81.437696, 30.284657],
[-81.438134, 30.283427],
[-81.439935, 30.281191],
[-81.440578, 30.279729],
[-81.440309, 30.276152],
[-81.441217, 30.271746],
[-81.440891, 30.270368],
[-81.440247, 30.269313],
[-81.438555, 30.267721],
[-81.43765, 30.266188],
[-81.43705, 30.257116],
[-81.441869, 30.256519],
[-81.45385, 30.252008],
[-81.466184, 30.251073],
[-81.472173, 30.251296],
[-81.491372, 30.251034],
[-81.507105, 30.253603],
[-81.510744, 30.253761],
[-81.530261, 30.250144],
[-81.56957, 30.249854],
[-81.584658, 30.251369],
[-81.586895, 30.251326],
[-81.589607, 30.250593],
[-81.593308, 30.248471],
[-81.605497, 30.260294],
[-81.621493, 30.282334],
[-81.621199, 30.282314]
]
]
]
}
It should create an odd shape overlay in the Southside of Jacksonville,FL, but it isn't. When the completion block is called the Coordinates are added to the array, but the map overlay isn't showing. Any thoughts?
Well this is somewhat embarrassing. I did as was suggested in the comments, and tried having the shape with nine vertices. It still didn't work. I then changed the coord from:
print("LATITUDE: \(coord[0])", "LONGITUDE: \(coord[1])")
let coordinatesToAppend = CLLocationCoordinate2D(latitude: coord[0], longitude: coord[1])
to:
print("LATITUDE: \(coord[1])", "LONGITUDE: \(coord[0])")
let coordinatesToAppend = CLLocationCoordinate2D(latitude: coord[1], longitude: coord[0])
It works perfectly. Turns out I had the Latitude and Longitude wrong.