UICollectionView reloadData() crashes after emtying datasource dictionary - swift3

I have a UICollectionView which takes the results of an API search. The search is triggered by the following code. The results are appended to a dictionary [[String: Any]] and I call self.collectionView.reloadData() after my query completes.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
var newValue = textField.text!
let location = min(range.location, newValue.characters.count)
let startIndex = newValue.characters.index(newValue.startIndex, offsetBy: location)
let endIndex = newValue.characters.index(newValue.startIndex, offsetBy: location + range.length)
let newRangeValue = Range<String.Index>(startIndex ..< endIndex)
newValue.replaceSubrange(newRangeValue, with: string)
searchView.searchFieldValueChanged(newValue)
return true
}
Then, if I want to change the search string and search again I want to empty the dictionary and call reloadData() again I get an app crash.
The error is
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UICollectionView received layout attributes for a cell with an index path that does not exist:
Here is my datasource implementation
var searchResults = [[String: Any]]()
let layout: UICollectionViewFlowLayout = {
let layout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10)
layout.estimatedItemSize.height = 200
layout.estimatedItemSize.width = 200
layout.minimumInteritemSpacing = 10
layout.minimumLineSpacing = 10
return layout
}()
collectionView = UICollectionView(frame: self.frame, collectionViewLayout: layout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(LanesCollectionViewCell.self, forCellWithReuseIdentifier: cellId)
collectionView.backgroundColor = .yellow // Constants.APP_BACKGROUND_COLOR
collectionView.alwaysBounceVertical = true
collectionView.clipsToBounds = true
collectionView.translatesAutoresizingMaskIntoConstraints = false
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if searchResults.count == 0 {
collectionView.alpha = 0.0
} else {
collectionView.alpha = 1.0
}
return searchResults.count
}
after query
func parseMoreData(jsonData: [String: Any]) {
let items = jsonData["items"] as! [[String: Any]]
self.collectionView.layoutIfNeeded()
self.collectionView.reloadData()
}
}
func searchFieldValueChanged(_ textValue: String) {
searchResults = []

This looks fixed by using this instead of layoutIfNeeded()
collectionView.reloadData()
collectionView.collectionViewLayout.invalidateLayout()

Related

ARKit and RealityKit - ARSessionDelegate is retaining 14 ARFrames

I am classifying images per frame from ARSession delegate by Vision framework and CoreML in an Augmented Reality app, with ARKit and RealityKit. While processing a frame.capturedImage I am not requesting another frame.capturedImage for performance.
The camera is not giving smooth experience, it gets stuck time to time. Seems like a frame loss.
And I am getting this Warning:
[Session] ARSession <0x122cc3710>: ARSessionDelegate is retaining 14 ARFrames. This can lead to future camera frames being dropped.
My Codes:
import Foundation
import SwiftUI
import RealityKit
import ARKit
import CoreML
struct ARViewContainer: UIViewRepresentable {
var errorFunc: ()->Void
var frameUpdateFunc: ()->Void
#Binding var finalLabel:String
func makeUIView(context: Context) -> ARView {
let arView = ARView(frame: .zero)
let config = ARWorldTrackingConfiguration()
config.planeDetection = [.horizontal,.vertical]
config.environmentTexturing = .automatic
if ARWorldTrackingConfiguration.supportsSceneReconstruction(.mesh){
config.sceneReconstruction = .mesh
}
arView.session.delegate = context.coordinator
arView.session.run(config)
context.coordinator.myView = arView
return arView
}
func updateUIView(_ uiView: ARView, context: Context) {
}
func makeCoordinator() -> Coordinator {
Coordinator(finalLabel: $finalLabel, self, funct: self.errorFunc, frameUpdateFunc: self.frameUpdateFunc)
}
class Coordinator: NSObject, ARSessionDelegate {
var objectDetectionService = ObjectDetectionService()
var myView:ARView?
#Binding var finalLabel:String
var parent: ARViewContainer
var efunc:()->Void
var frameUpdateFunc:()->Void
var isLoopShouldContinue = true
var lastLocation: SCNVector3?
//let model = try? MobileNetV2(configuration: .init())
private let classifier = VisionClasifier(mlModel: try? MobileNetV2(configuration: .init()).model)
private var currentBuffer: CVPixelBuffer? = nil
init(finalLabel:Binding<String>,_ arView: ARViewContainer,funct: #escaping ()->Void, frameUpdateFunc:#escaping ()->Void) {
parent = arView
self.efunc = funct
self.frameUpdateFunc = frameUpdateFunc
_finalLabel = finalLabel
}
func session(_ session: ARSession, didFailWithError error: Error) {
//print("Error Tanvir: ",error)
self.efunc()
}
func session(_ session: ARSession, didUpdate frame: ARFrame) {
if isLoopShouldContinue{
self.classifyFrame(currentFrame: frame)
}
let transform = SCNMatrix4(frame.camera.transform)
let orientation = SCNVector3(-transform.m31, -transform.m32, transform.m33)
let location = SCNVector3(transform.m41, transform.m42, transform.m43)
let currentPositionOfCamera = orientation + location
if let lastLocation = lastLocation {
let speed = (lastLocation - currentPositionOfCamera).length()
isLoopShouldContinue = speed < 0.0025
}
lastLocation = currentPositionOfCamera
}
// When ARKit detects a new anchor, it will add it to the ARSession
// Whenever there is a newly added ARAnchor, you will get that anchor here.
// In this short tutorial, we will target the ARPlaneAnchor, and use the information stored
// in that anchor for visualization.
func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
guard let myView = myView else {
return
}
for anchor in anchors {
if anchor is ARPlaneAnchor {
let planeAnchor = anchor as! ARPlaneAnchor
//addPlaneEntity(with: planeAnchor, to: myView)
}
}
}
// ARKit will automatically track and update the ARPlaneAnchor.
// We use that anchor to update the `skin` of the plane.
func session(_ session: ARSession, didUpdate anchors: [ARAnchor]) {
guard let myView = myView else {
return
}
for anchor in anchors {
if anchor is ARPlaneAnchor {
let planeAnchor = anchor as! ARPlaneAnchor
//updatePlaneEntity(with: planeAnchor, in: myView)
}
}
}
// When ARKit remove an anchor from the ARSession, you will get the removed
// anchor here.
func session(_ session: ARSession, didRemove anchors: [ARAnchor]) {
guard let myView = myView else {
return
}
for anchor in anchors {
if anchor is ARPlaneAnchor {
let planeAnchor = anchor as! ARPlaneAnchor
//removePlaneEntity(with: planeAnchor, from: myView)
}
}
}
func addAnnotation(rectOfInterest rect: CGRect, text: String,width:Float,height:Float) {
let point = CGPoint(x: rect.midX, y: rect.midY)
print("point:", point)
//let scnHitTestResults = myView.hitTest(point,
// options: [SCNHitTestOption.searchMode: SCNHitTestSearchMode.all.rawValue])
//guard !scnHitTestResults.contains(where: { $0.node.name == BubbleNode.name }) else { return }
let raycastResult = myView!.raycast(from: point, allowing: .estimatedPlane, alignment: .any)
// guard let raycastQuery = myView!.raycastQuery(from: point,
// allowing: .existingPlaneInfinite,
// alignment: .horizontal),
// let raycastResult = myView.session.raycast(raycastQuery).first else { return }
guard let raycastResult = raycastResult.first else{
print("raycast result failed")
return
}
let anchorExists = myView!.scene.anchors.contains(where: {$0.name == text})
guard anchorExists == false else{
print("anchor Already exists")
return
}
let position = raycastResult.worldTransform.columns.3
let myEntity = create2dEntity(with: position, boundingBox: rect, raycastResult: raycastResult,width:width ,height:height)
let planeAnchorEntity = AnchorEntity()
planeAnchorEntity.name = text
planeAnchorEntity.position = simd_make_float3(position)
planeAnchorEntity.addChild(myEntity)
// Finally, add the entity to scene.
myView!.scene.addAnchor(planeAnchorEntity)
print("anchor added: ", planeAnchorEntity.name)
}
func classifyFrame(currentFrame:ARFrame){
//let currentImageName = photos[currentIndex]
// 2
// 3
print("inside Classify")
//print("CurrentBuffer", currentBuffer)
guard self.currentBuffer == nil else {
//print("CurrentBuffer: ",currentBuffer)
//self.finalLabel = "current buffer problem"
return
}
self.currentBuffer = currentFrame.capturedImage
// guard let model = self.model else {
// return "Model not Found."
// }
let img = CIImage(cvImageBuffer: currentFrame.capturedImage)
let cgImage = convertCIImageToCGImage(inputImage: img)
guard let cgImage = cgImage else{
print("can not convert CGImage")
self.finalLabel = "can not convert CGImage"
return
}
objectDetectionService.detect(on: .init(pixelBuffer: currentFrame.capturedImage)) { [weak self] result in
guard let self = self else { return }
switch result {
case .success(let response):
self.finalLabel = response.classification.description
print("Real Width: ",response.boundingBox.width)
let rectOfInterest = VNImageRectForNormalizedRect(
response.boundingBox,
Int(self.myView!.bounds.width),
Int(self.myView!.bounds.height))
self.addAnnotation(rectOfInterest: rectOfInterest, text: response.classification.description,width: Float(response.boundingBox.width),height: Float(response.boundingBox.height))
print("Success:",response.classification.description)
self.currentBuffer = nil
case .failure(let error):
self.finalLabel = "Detection Failed"
print("Detection failure: ",error.localizedDescription)
self.currentBuffer = nil
break
}
}
}
}
}
func convertCIImageToCGImage(inputImage: CIImage) -> CGImage? {
let context = CIContext(options: nil)
if let cgImage = context.createCGImage(inputImage, from: inputImage.extent) {
return cgImage
}
return nil
}
// The ARPlaneAnchor contains the information we need to create the `skin` of the plane.
func addPlaneEntity(with anchor: ARPlaneAnchor, to view: ARView) {
let planeAnchorEntity = AnchorEntity(.plane([.any],
classification: [.any],
minimumBounds: [0.01, 0.01]))
let planeModelEntity = createPlaneModelEntity(with: anchor)
// Give Entity a name for tracking.
planeAnchorEntity.name = anchor.identifier.uuidString + "_anchor"
planeModelEntity.name = anchor.identifier.uuidString + "_model"
// Add ModelEntity as a child of AnchorEntity.
// AnchorEntity handles `position` of the plane.
// ModelEntity handles the `skin` of the plane.
planeAnchorEntity.addChild(planeModelEntity)
// Finally, add the entity to scene.
view.scene.addAnchor(planeAnchorEntity)
}
func create2dEntity(with position: simd_float4, boundingBox: CGRect, raycastResult:ARRaycastResult, width:Float,height:Float ) -> ModelEntity{
var planeMesh: MeshResource
var color: UIColor
print("horizotal plane")
color = UIColor.red.withAlphaComponent(0.5)
print("Constant width: 0.1 but BoundingBox Width: ",boundingBox.width)
planeMesh = .generatePlane(width: 0.1, height: 0.1)
return ModelEntity(mesh: planeMesh, materials: [SimpleMaterial(color: color, roughness: 0.25, isMetallic: false)])
}
func createPlaneModelEntity(with anchor: ARPlaneAnchor) -> ModelEntity {
var planeMesh: MeshResource
var color: UIColor
if anchor.alignment == .horizontal {
print("horizotal plane")
color = UIColor.blue.withAlphaComponent(0.5)
planeMesh = .generatePlane(width: anchor.extent.x, depth: anchor.extent.z)
} else if anchor.alignment == .vertical {
print("vertical plane")
color = UIColor.yellow.withAlphaComponent(0.5)
planeMesh = .generatePlane(width: anchor.extent.x, height: anchor.extent.z)
} else {
fatalError("Anchor is not ARPlaneAnchor")
}
return ModelEntity(mesh: planeMesh, materials: [SimpleMaterial(color: color, roughness: 0.25, isMetallic: false)])
}
func removePlaneEntity(with anchor: ARPlaneAnchor, from arView: ARView) {
guard let planeAnchorEntity = arView.scene.findEntity(named: anchor.identifier.uuidString+"_anchor") else { return }
arView.scene.removeAnchor(planeAnchorEntity as! AnchorEntity)
}
func updatePlaneEntity(with anchor: ARPlaneAnchor, in view: ARView) {
var planeMesh: MeshResource
guard let entity = view.scene.findEntity(named: anchor.identifier.uuidString+"_model") else { return }
let modelEntity = entity as! ModelEntity
if anchor.alignment == .horizontal {
planeMesh = .generatePlane(width: anchor.extent.x, depth: anchor.extent.z)
} else if anchor.alignment == .vertical {
planeMesh = .generatePlane(width: anchor.extent.x, height: anchor.extent.z)
} else {
fatalError("Anchor is not ARPlaneAnchor")
}
modelEntity.model!.mesh = planeMesh
}
import SceneKit
extension SCNVector3 {
func length() -> Float {
return sqrtf(x * x + y * y + z * z)
}
}
func -(l: SCNVector3, r: SCNVector3) -> SCNVector3 {
return SCNVector3Make(l.x - r.x, l.y - r.y, l.z - r.z)
}
func +(l: SCNVector3, r: SCNVector3) -> SCNVector3 {
return SCNVector3(l.x + r.x, l.y + r.y, l.z + r.z)
}
func /(l: SCNVector3, r: Float) -> SCNVector3 {
return SCNVector3(l.x / r, l.y / r, l.z / r)
}
Detection: (Here is the problem, I guess, in detect method)
import Foundation
import UIKit
import CoreML
import Vision
import SceneKit
class ObjectDetectionService {
var mlModel = try! VNCoreMLModel(for: YOLOv3Int8LUT().model)
//let model = try? YOLOv3Int8LUT(configuration: .init())
lazy var coreMLRequest: VNCoreMLRequest = {
return VNCoreMLRequest(model: mlModel,
completionHandler: self.coreMlRequestHandler)
}()
private var completion: ((Result<Response, Error>) -> Void)?
func detect(on request: Request, completion: #escaping (Result<Response, Error>) -> Void) {
self.completion = completion
//let orientation = .up
let imageRequestHandler = VNImageRequestHandler(cvPixelBuffer: request.pixelBuffer)
do {
try imageRequestHandler.perform([coreMLRequest])
} catch {
self.complete(.failure(error))
return
}
}
}
private extension ObjectDetectionService {
func coreMlRequestHandler(_ request: VNRequest?, error: Error?) {
if let error = error {
complete(.failure(error))
return
}
guard let request = request, let results = request.results as? [VNRecognizedObjectObservation] else {
complete(.failure(RecognitionError.resultIsEmpty))
return
}
guard let result = results.first(where: { $0.confidence > 0.8 }),
let classification = result.labels.first else {
complete(.failure(RecognitionError.lowConfidence))
return
}
let response = Response(boundingBox: result.boundingBox,
classification: classification.identifier)
complete(.success(response))
}
func complete(_ result: Result<Response, Error>) {
DispatchQueue.main.async {
self.completion?(result)
self.completion = nil
}
}
}
enum RecognitionError: Error {
case unableToInitializeCoreMLModel
case resultIsEmpty
case lowConfidence
}
extension ObjectDetectionService {
struct Request {
let pixelBuffer: CVPixelBuffer
}
struct Response {
let boundingBox: CGRect
let classification: String
}
}
Why am I getting this warning, and How to get the camera smooth experience?
The session(_ session: ARSession, didUpdate frame: ARFrame) delegate method is called very frequently: many times per second. If your classifyFrame method is doing too much work, it will retain the ARFrame object until after the next frame is delivered to the delegate.
ARKit will warn you when too many frames are retained, typically because a queue is blocked in your delegate.

How to keep reference of data when using ObservableObject

I am new to Swiftui and I struggle to understand how to properly retain data created in ObservableObject when rendering views? Or a completely different approach to the problem maybe?
More specifically, it is about getting HTTP data in each row in a List().
Right now, it makes the HTTP call far too often when parent views are rendered, which causes all rows to be reloaded.
The same issue can be found here: Keep reference on view/data model after View update
public class VideoFetcher: ObservableObject {
#Published var video: VideoResponse?
#Published var coverImage: UIImage?
#Published var coverImageLoading = false
#Published var categories: String?
#Published var loading = false
#Published var error = false
func load(mediaItemSlug: String = "", broadcasterSlug: String = "") {
self.loading = true
Video.findBySlug(
mediaItemSlug: mediaItemSlug,
broadcasterSlug: broadcasterSlug,
successCallback: {video -> Void in
self.video = video
self.loading = false
self.setCategories()
self.loadCoverImage()
},
errorCallback: {(error, _) -> Void in
self.loading = false
self.error = true
})
}
func loadCoverImage() {
guard self.video!.coverImageUrl != "" else {
return
}
self.coverImageLoading = true
let downloader = ImageDownloader()
let urlRequest = URLRequest(url: URL(string: self.video!.coverImageUrl)!)
let filter = AspectScaledToFillSizeFilter(size: CGSize(width: 520.0, height: 292.499999963))
downloader.download(urlRequest, filter: filter) { response in
if case .success(let image) = response.result {
self.coverImage = image
self.coverImageLoading = false
}
}
}
func setCategories() {
if (self.video!.broadcaster.categories.count > 0) {
let categoryNames = self.video!.broadcaster.categories.map { category in
return category.name == "" ? "(no name)" : category.name
}
self.categories = categoryNames.joined(separator: " • ");
}
}
}
List() row:
struct VideoCard: View {
#ObservedObject var fetcher = VideoFetcher()
...
init() {
// Causes reload each render
self.fetcher.load()
}
var body: some View {
...
.onAppear {
// Loads that on appear but fetcher.video is nil after view re-rendered because load() wasn't called
self.fetcher.load()
}
}
}
Thanks, Chris. I thought I was doing something wrong on an architectural level but I added caching and that solved my problem.
import Alamofire
import AlamofireImage
import Cache
public class VideoFetcher: ObservableObject {
#Published var video: VideoResponse?
#Published var coverImage: UIImage?
#Published var coverImageLoading = false
#Published var broadcasterImage: UIImage?
#Published var categories: String?
#Published var loading = false
#Published var error = false
func load(mediaItemSlug: String = "", broadcasterSlug: String = "") {
let videoCache = try? AppCache.video!.object(forKey: mediaItemSlug)
if (videoCache != nil) {
self.video = videoCache
self.setCategories()
self.loadCoverImage()
return
}
self.loading = true
Video.findBySlug(
mediaItemSlug: mediaItemSlug,
broadcasterSlug: broadcasterSlug,
successCallback: {video -> Void in
try? AppCache.video!.setObject(video, forKey: mediaItemSlug)
self.video = video
self.loading = false
self.setCategories()
self.loadCoverImage()
self.loadBroadcasterImage()
},
errorCallback: {(error, _) -> Void in
self.loading = false
self.error = true
})
}
func loadCoverImage() {
let coverImageUrl = self.video!.coverImageUrl
guard coverImageUrl != "" else {
return
}
let urlRequest = URLRequest(url: URL(string: coverImageUrl)!)
let cachedImage = AppCache.image!.image(for: urlRequest, withIdentifier: coverImageUrl)
if (cachedImage != nil) {
self.coverImage = cachedImage
return
}
self.coverImageLoading = true
let downloader = ImageDownloader(imageCache: AppCache.image!)
let filter = AspectScaledToFillSizeFilter(size: CGSize(width: 520.0, height: 292.499999963))
downloader.download(urlRequest, filter: filter) { response in
if case .success(let image) = response.result {
AppCache.image!.add(image, for: urlRequest, withIdentifier: coverImageUrl)
self.coverImage = image
self.coverImageLoading = false
}
}
}
func loadBroadcasterImage() {
let broadcasterImage = self.video!.broadcaster.avatarImageUrl
guard broadcasterImage != "" else {
return
}
let urlRequest = URLRequest(url: URL(string: broadcasterImage)!)
let cachedImage = AppCache.image!.image(for: urlRequest, withIdentifier: broadcasterImage)
if (cachedImage != nil) {
self.broadcasterImage = cachedImage
return
}
let downloader = ImageDownloader(imageCache: AppCache.image!)
let filter = AspectScaledToFillSizeFilter(size: CGSize(width: 16, height: 16))
downloader.download(urlRequest, filter: filter) { response in
if case .success(var image) = response.result {
image = image.af.imageRoundedIntoCircle()
AppCache.image!.add(image, for: urlRequest, withIdentifier: broadcasterImage)
self.broadcasterImage = image
}
}
}
func setCategories() {
let categories = self.video!.broadcaster.categories
if (categories.count > 0) {
let categoryNames = categories.map { category in
return category.name == "" ? "(no name)" : category.name
}
self.categories = categoryNames.joined(separator: " • ");
}
}
}

how to set the maximum 4 digit number in uitextfield using swift3.0

I use this code but it should return anyone. But I need to use this two scenarios. So how can I change this? Anyone help me. I restrict a (.0 symbol and at the same time only allowed 4 digit amount. How can i do this.???
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let text = amountField.text else { return true }
let newLength = text.characters.count + string.characters.count - range.length
return newLength <= 4 // Bool
let ACCEPTABLE_CHARACTERS = "1234567890"
let cs = CharacterSet(charactersIn: ACCEPTABLE_CHARACTERS).inverted
let filtered: String = string.components(separatedBy: cs).joined(separator: "")
return string == filtered
}
Possible solution for your issue.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
var newLength = string.utf16.count - range.length
if let text = textfield.text {
newLength = text.utf16.count + string.utf16.count - range.length
}
let characterSet = NSMutableCharacterSet()
characterSet.addCharacters(in: "1234567890")
if string.rangeOfCharacter(from: characterSet.inverted) != nil || newLength > 4 {
return false
}
return true
}

How to draw a Route between CurrentLocation to SearchedLocation in MkMapView in Swift

I need current location as a source and searched location as a destination, but I got the current location but here I am unable to bring coordinates(latitude and longitude) from searched location to destination.
here my destination shows nil why?
Below is the code please help me.
import UIKit
import MapKit
import CoreLocation
class MapSampViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate, UISearchBarDelegate {
//Privacy - Location When In Use Usage Description, Privacy - Location Always Usage Description-------these two add in info.plist
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var mapView: MKMapView!
var source: CLLocationCoordinate2D!
var destination: CLLocationCoordinate2D!
var myaddress:String!
var mycity:String!
var mystate:String!
var mycountry:String!
var mytitle:String!
var mylongitude:String!
var mylatitude:String!
var locationtoSearch:String!
let locationManager = CLLocationManager()
var currentlocationPlacemark: CLPlacemark!
override func viewDidLoad() {
super.viewDidLoad()
searchBar.delegate = self
mapView.delegate = self
mapView.showsScale = true
mapView.showsPointsOfInterest = true
mapView.showsUserLocation = true
if CLLocationManager.locationServicesEnabled()
{
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
// self.showDirection()
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
locationtoSearch = self.searchBar.text
var geocoder:CLGeocoder = CLGeocoder()
geocoder.geocodeAddressString(locationtoSearch!, completionHandler: {(placemarks, error) -> Void in
if((error) != nil)
{
print("Error", error)
}
else if let placemark = placemarks?[0] as? CLPlacemark {
var coordinates:CLLocationCoordinate2D = placemark.location!.coordinate
var pointAnnotation:MKPointAnnotation = MKPointAnnotation()
pointAnnotation.coordinate = coordinates
print(coordinates)
// pointAnnotation.title = "\(String(describing: placemark.name)),\(String(describing: placemark.locality)), \(String(describing: placemark.administrativeArea)), \(String(describing: placemark.country))"
self.myaddress = placemark.name
self.mycity = placemark.locality
self.mystate = placemark.administrativeArea
self.mycountry = placemark.country
pointAnnotation.title = "\(self.myaddress),\(self.mycity),\(self.mystate),\(self.mycountry)"
self.mylongitude = String(stringInterpolationSegment: placemark.location?.coordinate.longitude)
self.mylatitude = String(stringInterpolationSegment: placemark.location?.coordinate.latitude)
self.mapView?.addAnnotation(pointAnnotation)
self.mapView?.centerCoordinate = coordinates
print("coordinates \(coordinates)")
print("The latitude \(self.mylatitude)")
print("The longitude \(self.mylongitude)")
self.mapView?.selectAnnotation(pointAnnotation, animated: true)
}
})
self.showDirection()//i called here or in view viewDidLoad
let annotationsToRemove = mapView.annotations.filter { $0 !== self.mapView.userLocation
}
mapView.removeAnnotations( annotationsToRemove )
}
func showDirection()
{
source = locationManager.location?.coordinate//17.6881° N, 83.2131° E
// let destination = CLLocationCoordinate2DMake(24.9511, 121.2358 )//If i give like this its working
destination = CLLocationCoordinate2DMake(Double(mylongitude)!, Double(mylongitude)!)//fatal error: unexpectedly found nil while unwrapping an Optional value
let sourcePlacemark = MKPlacemark(coordinate: source!)
let destinationPlacemark = MKPlacemark(coordinate: destination)
let sourceItem = MKMapItem(placemark: sourcePlacemark)
let destinationItem = MKMapItem(placemark: destinationPlacemark)
let directionReq = MKDirectionsRequest()
directionReq.source = sourceItem
directionReq.destination = destinationItem
directionReq.transportType = .automobile
let directions = MKDirections(request: directionReq)
directions.calculate(completionHandler: {(response, error) in
if error != nil {
print("Error getting directions")
}
else {
let route = response?.routes[0]
self.mapView.add((route?.polyline)!, level:.aboveRoads)
let rekt = route?.polyline.boundingMapRect
self.mapView.setRegion(MKCoordinateRegionForMapRect(rekt!), animated: true)
}
})
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let rendrer = MKPolylineRenderer(overlay: overlay)
rendrer.strokeColor = UIColor.blue
rendrer.lineWidth = 3
return rendrer
}
}
here i called showDirection() func in searchBarSearchButtonClicked but it is getting called before coming here why?
Direction requests are executed asynchronously. This means that the rest of your app doesn't wait for the direction to be fetched.
Your showDirection function is both fetching the direction and adding it to the mapView. It would be best to separate these functionalities. You can fetch the direction, update a route variable and have an observer on it which will add the route to the map once it has been fetched.
#IBOutlet weak var mapView: MKMapView!
var route: MKRoute? {
didSet {
mapView.add((route?.polyline)!, level:.aboveRoads) }
}

TableView crashes under uncaught exception 'NSUnknownKeyException', this class is not key value coding-compliant for the key x.'

I can't seem to make this tableView with custom cells work. I get a runtime error
Terminating app due to uncaught exception 'NSUnknownKeyException',
reason: '[ setValue:forUndefinedKey:]: this class is not key
value coding-compliant for the key causeCampaignDescription.'
The weird thing is that that property is not called like that anymore. This is the cell file MainViewControllerTableViewCell
//
// MainViewControllerTableViewCell.swift
//
//
// Created by on 9/13/17.
// Copyright © 201. All rights reserved.
//
import UIKit
class MainViewControllerTableViewCell: UITableViewCell {
#IBOutlet weak var causeCampaignImageView: UIImageView!
#IBOutlet weak var causeDescription: UILabel!
#IBOutlet weak var daysToFinishLabel: UILabel!
#IBOutlet weak var raisedOverTotalLabel: UILabel!
#IBOutlet weak var percentageCompletedLabel: UILabel!
#IBOutlet weak var goalProgresView: UIProgressView!
//card used on
#IBInspectable var cornerradius : CGFloat = 2
#IBInspectable var shadowOffSetWidth : CGFloat = 0
#IBInspectable var shadowOffSetHeight : CGFloat = 5
#IBInspectable var shadowColor : UIColor = UIColor.black
#IBInspectable var shadowOpacity : CGFloat = 0.5
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
override func layoutSubviews() {
layer.cornerRadius = cornerradius
layer.shadowColor = shadowColor.cgColor
layer.shadowOffset = CGSize(width: shadowOffSetWidth, height: shadowOffSetHeight)
let shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: cornerradius)
layer.shadowPath = shadowPath.cgPath
layer.shadowOpacity = Float(shadowOpacity)
}
}
and this is the view controller that holds the table view MainViewController:
//
// ViewController.swift
//
//
// Created by on 1/28/17.
// Copyright © 2017. All rights reserved.
//
import UIKit
import Alamofire
import SwiftyJSON
import Firebase
class MainViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
var campaignRowsData = [CauseCampaign]()
var serverFetchCampaignsUrl = Config.Global._serverUrl
#IBOutlet weak var campaignTableView: UITableView!
//show navigation controller bar
var facebookID = "", twitterID = "",firebaseID = ""
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//hide bar from navigation controller
setToolbar()
campaignTableView.delegate=self
campaignTableView.dataSource=self
campaignTableView.separatorColor = UIColor(white: 0.95, alpha: 1)
recoverUserDefaults()
getCampaignList()
//print(facebookID, twitterID, firebaseID)
}
func setToolbar(){
//hide bar from navigation controller
self.navigationController?.isNavigationBarHidden = false
self.navigationItem.setHidesBackButton(true, animated: false)
self.navigationController?.navigationBar.barTintColor = UIColor.purple
}
func getCampaignList(){
Alamofire.request(serverFetchCampaignsUrl+"/campaigns/get/all/user/\(twitterID)/firebase/\(firebaseID)/cat/0", method: .get).validate().responseJSON { response in
switch response.result {
case .success(let data):
let campaignCausesJSON = JSON(campaignCausesData: data)
self.parseCampaignCausesListResponse(campaignCausesJSON)
//alternative thread operation
DispatchQueue.main.async {
self.campaignTableView.reloadData()
}
case .failure(let error):
print(error)
}
}
}
func parseCampaignCausesListResponse(_ campaignCausesJSON:JSON){
if let activeCampaignCount = campaignCausesJSON["active_campaigns_count"].string {
//Now you got your value
print("TOTAL_ACTIVE_CAMPAIGNS",activeCampaignCount)
CampaignsGlobalDataManagerUtil.campaignTotalCount = Int(activeCampaignCount)!
}
if let contributorUserId = campaignCausesJSON["contributor_user_id"].string {
//Now you got your value
print("CONTRIBUTOR_USER_ID",contributorUserId)
CurrentUserUtil.contributorUserId = contributorUserId
}
if let userTwitterFollowersQty = campaignCausesJSON["user_twitter_followers_qty"].int {
//Now you got your value
print("USER_TWITTER_FOLLOWERS_QTY",userTwitterFollowersQty)
CurrentUserUtil.twitterFollowersCount = Int(userTwitterFollowersQty)
}
//Parsing campaigns object array
campaignCausesJSON["camp_array"].arrayValue.map({
let campaignCause:JSON = $0
parseCampaign(campaignCause)
})
}
//TODO:CHANGE TO DATATAPE OBJECT
func parseCampaign(_ causeCampaign:JSON){
let causeCampaignObject: CauseCampaign = CauseCampaign();
causeCampaignObject.description = causeCampaign["cause_description"].stringValue
causeCampaignObject.id = causeCampaign["campaign_id"].stringValue
if let contributorsQty = causeCampaign["contributors_qty"].int{
causeCampaignObject.contributorsQty = contributorsQty
}
causeCampaignObject.currencySymbol = causeCampaign["currency_symbol"].stringValue
if let currentContributions = causeCampaign["current_contributions"].float{
causeCampaignObject.currentContributions = currentContributions
}
if let goal = causeCampaign["goal"].float {
causeCampaignObject.goal = goal
}
if let goalPercentageAchieved = causeCampaign["goal_percentage_achieved"].float{
causeCampaignObject.goalPercentageAchieved = causeCampaign["goal_percentage_achieved"].float!
}
causeCampaignObject.hashtag = causeCampaign["hashtag"].stringValue
causeCampaignObject.name = causeCampaign["name"].stringValue
if let remainingAmmountToGoal = causeCampaign["remaining_ammount_to_goal"].float{
causeCampaignObject.remainingAmmountToGoal = remainingAmmountToGoal
}
if let picUrl = causeCampaign["pic_url"].stringValue as? String {
causeCampaignObject.picUrl = picUrl
}
if let campaignStartingDate = causeCampaign["created_at"].string{
causeCampaignObject.campaignStartingDate = campaignStartingDate
}
if let campaignEndingDate = causeCampaign["campaign_ending_date"].string{
causeCampaignObject.campaignEndingDate = campaignEndingDate
}
var foundationsArray = [Foundation]()
causeCampaign["foundations"].arrayValue.map({
let id = $0["foundation_id"].stringValue
let twitterUsername = $0["twitter_username"].stringValue
let picPath = $0["pic_path"].stringValue
let name = $0["name"].stringValue
let foundation:Foundation = Foundation(id,twitterAccount: twitterUsername,picPath: picPath,name: name)
foundationsArray.append(foundation)
})
causeCampaignObject.foundations = foundationsArray
campaignRowsData.append(causeCampaignObject)
// foundations = "<null>";
//innecesario
// SACAR DE LA REQUEST INICIAL???
// "went_inactive_date" = "<null>";
// "tweet_id" = 900936910494810112;
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return campaignRowsData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = campaignTableView.dequeueReusableCell(withIdentifier: "campaignCell", for: indexPath) as! MainViewControllerTableViewCell
//setting card attributes
print("ROW",campaignRowsData[indexPath.row].description)
let campaignCause:CauseCampaign = campaignRowsData[indexPath.row]
if let desc = campaignCause.description as? String{
cell.causeDescription.text = desc
} else {
print("NULL")
}
return cell
}
func recoverUserDefaults(){
if let fbID = UserDefaults.standard.object(forKey: Config.Global._facebookIdUserDefaults) as? String {
facebookID = fbID
}else{
print("FACEBOOK ID IS NULL")
}
if let twtID = UserDefaults.standard.object(forKey: Config.Global._twitterIdUserDefaults) as? String{
twitterID = twtID
}else{
print("TWITTER ID IS NULL")
}
if let firID = UserDefaults.standard.object(forKey: Config.Global._firebaseIdUserDefaults) as? String{
firebaseID = firID
}else{
print("TWITTER ID IS NULL")
}
return
}
}
The app crashes if the line reloadData is uncommented (I don't even know when and If I should use this)
If I set a label you can't see anything on screen, I see blank cards, but again, as soon as I uncomment reloadData it crashes
There's no causeCampaignDescription, now it's called causeDescription so I don't know why the error keeps mentioning that field
The data desc is ok since I printed it and it has the right content so it's not that
What could be the problem?
Searching the project for causeCampaignDescription will often turn up the offending xib and/or storyboard containing the outdated key path. However, it's been my experience that Xcode is not always 100% reliable about finding things in xibs and storyboards, so if Xcode's search feature won't find it, this command in the Terminal will turn it up straightaway:
find /path/to/your/project/directory -name .git -prune -or -type f -exec grep causeCampaignDescription {} \; -print
Once you find the offending item in the xib or storyboard, change it to the correct string and you should solve your problem.