Josh
Josh

Reputation: 25

Buggy UI Tableview

I want the view to be like Apple maps UI Tableview when you click on a map annotation, the UI Tableview moves up with the loaded information and it is smooth. I created a UITableView to load data from a Yelp API and Firebase database. While I do have the data loaded in the UI Tableview, there seems to be choppiness in the movement of the tableview. For example, when I first click on a map annotation, the UI Tableview will pop up, but in a random position and then after the Yelp API loads, it will move again to its default position. Another thing is that if I use a swipe gesture before the Yelp API loads, the UI Tableview will move accordingly, but then reset to its original position when the Yelp API data loads, which then I have to redo the swipe gesture.

There are many parts of this tableview, so I will provide a list of pieces of code that I use:

Note: The UI Tableview (locationInfoViews) is configured in the ViewDidLoad

Swiping up/down

 func configureGestureRecognizer() {
        let swipeUp = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipeGesture))
        swipeUp.direction = .up
        addGestureRecognizer(swipeUp)
        
        let swipeDown = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipeGesture))
        swipeDown.direction = .down
        addGestureRecognizer(swipeDown)
    }
    
    func animateInputView(targetPosition: CGFloat, completion: @escaping(Bool) -> ()) {
        UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: .curveEaseInOut, animations: {self.frame.origin.y = targetPosition}, completion: completion)
    }
    
// MARK: - Handlers
    
        @objc func handleSwipeGesture(sender: UISwipeGestureRecognizer) {
            if sender.direction == .up {
                if expansionState == .Disappeared {
                    animateInputView(targetPosition: self.frame.origin.y - 100) { (_) in
                        self.expansionState = .NotExpanded
                    }
                }
                
                if expansionState == .NotExpanded {
                    animateInputView(targetPosition: self.frame.origin.y - 200) { (_) in
                        self.expansionState = .PartiallyExpanded
                    }
                }
                
                if expansionState == .PartiallyExpanded {
                    animateInputView(targetPosition: self.frame.origin.y - 250) { (_) in
                        self.expansionState = .FullyExpanded
                    }
                }
                
            } else {
                if expansionState == .FullyExpanded {
                    animateInputView(targetPosition: self.frame.origin.y + 250) { (_) in
                        self.expansionState = .PartiallyExpanded
                    }
                }
                
                if expansionState == .PartiallyExpanded {
                    animateInputView(targetPosition: self.frame.origin.y + 200) { (_) in
                        self.expansionState = .NotExpanded
                    }
                }
                
                if expansionState == .NotExpanded {
                    animateInputView(targetPosition: self.frame.origin.y + 100) { (_) in
                        self.expansionState = .Disappeared
                    }
                }
            }
        }

When select annotation, information from Yelp and Firebase will be loaded into the location info view and the animation should move up

    func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
        self.locationPosts.removeAll()
        self.inLocationInfoMode = true
        
        if view.annotation is MKUserLocation {
            print("selected self")
            
        } else {
            
            self.selectedAnnotation = view.annotation as? Annotation
            let coordinates = selectedAnnotation.coordinate
            let coordinateRegion = MKCoordinateRegion(center: coordinates, latitudinalMeters: 1000, longitudinalMeters: 100)
            mapView.setRegion(coordinateRegion, animated: true)
            
            self.locationInfoViews.locationTitle = self.selectedAnnotation.title
            
            // Fetch Location Post
            guard let currentUid = Auth.auth().currentUser?.uid else {return}
            
            LOCATION_REF.child(self.selectedAnnotation.title).child(currentUid).observe(.childAdded) { (snapshot) in
                let postId = snapshot.key
                Database.fetchLocationPost(with: postId) { (locationPost) in
                    self.locationPosts.append(locationPost)
                    
                    self.locationPosts.sort { (post1, post2) -> Bool in
                        return post1.creationDate > post2.creationDate
                    }
                    
                    self.locationInfoViews.locationResults = self.locationPosts
//                    self.locationInfoViews.tableView.reloadData()
                    
                    // Fetch Location Information
                    guard let locationName = locationPost.locationName else {return}
                    guard let locationAddress = locationPost.address else {return}
                    
                    let locationRef = COORDINATES_REF.child(locationName).child(locationAddress)
                    
                    locationRef.child("mainTypeOfPlace").observe(.value) { (snapshot) in
                        guard let mainTypeOfPlace = snapshot.value as? String else {return}
//                        self.locationInfoViews.typeOfPlace = mainTypeOfPlace
                    
                    locationRef.child("shortAddress").observe(.value) { (snapshot) in
                        guard let address1 = snapshot.value as? String else {return}
                        
                        locationRef.child("city").observe(.value) { (snapshot) in
                            guard let city = snapshot.value as? String else {return}
                    
                            locationRef.child("state").observe(.value) { (snapshot) in
                                guard let state = snapshot.value as? String else {return}
                                
                                locationRef.child("countryCode").observe(.value) { (snapshot) in
                                    guard let country = snapshot.value as? String else {return}
                                    
                                    // fetch Yelp API Data
                                    self.service.request(.match(name: locationName, address1: address1, city: city, state: state, country: country)) {
                                        (result) in
                                        switch result {
                                        case .success(let response):
                                            let businessesResponse = try? JSONDecoder().decode(BusinessesResponse.self, from: response.data)
                                            let firstID = businessesResponse?.businesses.first?.id
                                            
                                            self.information.request(.BusinessID(id: firstID ?? "")) {
                                                (result) in
                                                switch result {
                                                case .success(let response):
                                                    if let jsonResponse = try? JSONSerialization.jsonObject(with: response.data, options: []) as? [String: Any] {
//                                                        print(jsonResponse)
                                                        
                                                        if let categories = jsonResponse["categories"] as? Array<Dictionary<String, AnyObject>> {
                                                            var mainCategory = ""
                                                            for category in categories {
                                                                mainCategory = category["title"] as? String ?? ""
                                                                break
                                                            }
                                                            self.locationInfoViews.typeOfPlace = mainCategory
                                                        }
                                                        
                                                        let price = jsonResponse["price"] as? String ?? ""
                                                
                                                        if let hours = jsonResponse["hours"] as? Array<Dictionary<String, AnyObject>> {
                                                            for hour in hours {
                                                                let isOpen = hour["is_open_now"] as? Int ?? 0
                                                                if isOpen == 1 {
                                                                    let attributedText = NSMutableAttributedString(string: "open ", attributes: [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 16), NSAttributedString.Key.foregroundColor: UIColor(rgb: 0x066C19)])
                                                                    attributedText.append(NSAttributedString(string: " \(price)", attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16), NSAttributedString.Key.foregroundColor: UIColor.black]))
                                                                    self.locationInfoViews.hoursLabel.attributedText = attributedText
                                                                } else {
                                                                    
                                                                    let attributedText = NSMutableAttributedString(string: "closed ", attributes: [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 16), NSAttributedString.Key.foregroundColor: UIColor.red])
                                                                    attributedText.append(NSAttributedString(string: " \(price)", attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16), NSAttributedString.Key.foregroundColor: UIColor.darkGray]))
                                                                    self.locationInfoViews.hoursLabel.attributedText = attributedText
                                                                }
                                                            }
                                                        }
                                                    }
                                                    
                                                    
                                                case .failure(let error):
                                                    print("Error: \(error)")
                                                }
                                            }
                                        case .failure(let error):
                                            print("Error: \(error)")
                                        }
                                    }
                                }
                            }
                            }
                        }
                    }
                    
                    self.locationInfoViews.tableView.reloadData()
                }
            }
            
            // enable the goButton
            if inLocationInfoMode {
                locationInfoViews.goButton.isEnabled = true
                locationInfoViews.coordinates = selectedAnnotation.coordinate
            }
            
            // the movement of the location info view
            if self.locationInfoViews.expansionState == .Disappeared {
                self.locationInfoViews.animateInputView(targetPosition: self.locationInfoViews.frame.origin.y - 335) { (_) in
                    self.locationInfoViews.expansionState = .PartiallyExpanded
                }
            }
        }
    }

Upvotes: 1

Views: 75

Answers (1)

Chiara
Chiara

Reputation: 398

Try adding a completion block for the fetching, first extract it into a separate function. On tap on the annotation show some loading state, while you load the data. Once you receive the completion block, decide what to do depending on the result. The fetching will be done while the user sees a loading state, and no flickering will occur.

Upvotes: 0

Related Questions