TableView doesn't refresh when created programmatically - swift

I programmatically created a UITableView that fills with data that I fetch from an API. When I fetched the data asynchronously, I try to update my table view—but it does not update.
I first create a UITableView within my UIViewController. The data I am trying to load does load correctly, it just comes in after the initial view has loaded. When I try to refresh my data by doing reloadData(), nothing happens.
What am I missing? Thanks!
Creating my controller:
class SpotifyTableViewController: UIViewController, UITableViewDelegate {
//table view programmatically added
let tableView = UITableView()
var safeArea: UILayoutGuide!
var playlists = [PlaylistItem]()
#objc func handleRefreshControl() {
// Update your content…
tableView.reloadData()
// Dismiss the refresh control.
DispatchQueue.main.async {
self.tableView.refreshControl?.endRefreshing()
}
}
My viewDidLoad() function
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
let refreshControl = UIRefreshControl()
tableView.refreshControl?.addTarget(tableView, action:
#selector(handleRefreshControl),
for: .valueChanged)
tableView.refreshControl = refreshControl
self.loadPlaylists(accessToken:
setupTableView()
}
Setting up the table:
func setupTableView() {
view.addSubview(tableView)
tableView.delegate = self
tableView.dataSource = self
}
func loadPlaylists(accessToken: String) {
.... make API call and retrieve data
let task = session.dataTask(with: url, completionHandler: { data, response, error in
DispatchQueue.main.async {
// update UI
...
}
}
DispatchQueue.main.async{
self.tableView.reloadData()
}
return
}
}
})
task.resume()
self.tableView.performSelector(onMainThread: Selector("reloadData"), with: nil, waitUntilDone: true)
}
}

the problem is you set tableview delegates after you reload tableview you must add tableview.reloadData() in setupTableView() in last line of code or you can first call setupTableView() and after that call self.loadPlaylists(accessToken:
super.viewDidLoad()
view.backgroundColor = .white
let refreshControl = UIRefreshControl()
tableView.refreshControl?.addTarget(tableView, action:
#selector(handleRefreshControl),
for: .valueChanged)
tableView.refreshControl = refreshControl
setupTableView()
self.loadPlaylists(accessToken:
or
func setupTableView() {
view.addSubview(tableView)
tableView.delegate = self
tableView.dataSource = self
tableView.reloadData()
}

Related

How to navigate from one View Controller to the other?

I want to navigate from one View Controller to another.
let vc = SecondViewController()
I have tried until now :
vc.modalPresentationController = .fullScreen
self.present(vc, animated: true) //self refers to the main view controller
Im trying to open a new ViewController when the users manages to register or to log in.I am new to software developing, and I want to ask, is this the best method to navigate from one ViewController to another, im asking because as I can see the mainViewController is not deinit(). I have found other similar questions and tried the answers, the problem is with the:
self.navigationController?.pushViewController
it doesn't work because I don't have any storyboard.
The question is it is right to navigate as explained above?
Thanks,
Typically when you are doing login you would use neither push or present. There are multiple ways of handling this, but the easiest is to embed in some parent (root) VC. Here is an example:
class ViewController: UIViewController {
private var embeddedViewController: UIViewController! {
didSet {
// https://developer.apple.com/documentation/uikit/view_controllers/creating_a_custom_container_view_controller
// Add the view controller to the container.
addChild(embeddedViewController)
view.addSubview(embeddedViewController.view)
// Create and activate the constraints for the child’s view.
embeddedViewController.view.translatesAutoresizingMaskIntoConstraints = false
embeddedViewController.view.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
embeddedViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
embeddedViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
embeddedViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
// Notify the child view controller that the move is complete.
embeddedViewController.didMove(toParent: self)
}
}
override func viewDidLoad() {
super.viewDidLoad()
let loginVC = LoginViewController()
loginVC.delegate = self
embeddedViewController = loginVC
}
}
extension ViewController: LoginDelegate {
func didLogin() {
embeddedViewController = MainViewController()
}
}
protocol LoginDelegate: AnyObject {
func didLogin()
}
class LoginViewController: UIViewController {
private lazy var loginButton: UIButton = {
let button = UIButton()
button.setTitle("Login", for: .normal)
button.addTarget(self, action: #selector(didTapLoginButton), for: .touchUpInside)
return button
}()
weak var delegate: LoginDelegate?
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(loginButton)
view.backgroundColor = .red
loginButton.translatesAutoresizingMaskIntoConstraints = false
loginButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
loginButton.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
#objc private func didTapLoginButton() {
delegate?.didLogin()
}
}
class MainViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .blue
}
}

Problem with delegates removing annotation

I have two screens. The first one (firstViewController) has a mapView with a UITapGestureRecognizer. When the user taps the screen, an annotations is added to the map and the second screen (secondViewController) is presented.
When the user dismisses the secondViewController and comes back to the first one, the annotation should be removed. I know I have to use delegation, but I just can't make it to work.
This is the code I have now:
class firstViewController: UIViewController, AnnotationDelegate {
let mapView = MKMapView()
var temporaryPinArray = [MKPointAnnotation]()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(mapView
let gesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
mapView.addGestureRecognizer(gesture)
secondVC.annotationDelegate = self
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
mapView.frame = view.bounds
}
#objc func handleTap(_ gestureReconizer: UILongPressGestureRecognizer) {
let location = gestureReconizer.location(in: mapView)
let coordinates = mapView.convert(location, toCoordinateFrom: mapView)
mapView.removeAnnotations(mapView.annotations)
let pin = MKPointAnnotation()
pin.coordinate = coordinates
temporaryPinArray.removeAll()
temporaryPinArray.append(pin)
mapView.addAnnotations(temporaryPinArray)
// Present secondViewController
let secondVC = SecondViewController()
panel.set(contentViewController: secondVC)
panel.addPanel(toParent: self)
}
func didRemoveAnnotation(annotation: MKPointAnnotation) {
mapView.removeAnnotation(annotation)
}
}
Second View Controller
protocol AnnotationDelegate {
func didRemoveAnnotation(annotation: [MKPointAnnotation])
}
class SecondViewController: UIViewController {
var annotationDelegate: AnnotationDelegate!
let mainVC = firstViewController()
let closeButton: UIButton = {
let button = UIButton()
button.backgroundColor = .grey
button.layer.cornerRadius = 15
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(closeButton)
closeButton.addTarget(self, action: #selector(dismissPanel), for: .touchUpInside)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
closeButton.frame = CGRect(x: view.frame.width-50, y: 10, width: 30, height: 30)
}
#objc func dismissPanel() {
self.dismiss(animated: true, completion: nil)
annotationDelegate.didRemoveAnnotation(annotation: mainVC.temporaryPinArray)
}
}
Thank you so much for your help!
You created a new instance of firstViewController inside SecondViewController. This instance is unrelated to the actual first one:
let mainVC = firstViewController()
This means that temporaryPinArray is different as well. So instead of passing in this unrelated array...
#objc func dismissPanel() {
self.dismiss(animated: true, completion: nil)
annotationDelegate.didRemoveAnnotation(annotation: mainVC.temporaryPinArray)
}
Just change the function to take no parameters instead:
protocol AnnotationDelegate {
func didRemoveAnnotation() /// no parameters
}
#objc func dismissPanel() {
self.dismiss(animated: true, completion: nil)
annotationDelegate.didRemoveAnnotation() /// no parameters
}
And inside firstViewController's didRemoveAnnotation, reference the actual temporaryPinArray.
func didRemoveAnnotation() {
mapView.removeAnnotations(temporaryPinArray) /// the current array
}

How to update RSS data in UITableView?

What I have: the project, written on SWIFT5, which is an rss reader (I use standard XMLParser ). I fill cells with data from parser. In order to update the data in cells I implemented UIRefreshControl and wrote objc method, which contains the same method(fetchData - see in code), as I use to get data, but it doesn't work. Moreover, this method is called only once, when app is launched. When I close app and then open, data is not updated... How can I deal with it?
What I want: when refreshControl is activated, data in cells should be updated
What I did: I declared a variable called refreshControl, add it to tableView and wrote a method #refresh related to control
import UIKit
class MainViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var table: UITableView!
private let url = "my url"
private var rssItems: [RSSItem]? {
didSet {
DispatchQueue.main.async {
self.table.reloadData()
}
}
}
var refreshControl = UIRefreshControl()
#objc func refresh (sender: UIRefreshControl) {
fetchData()
sender.endRefreshing()
}
override func viewDidLoad() {
super.viewDidLoad()
refreshControl.addTarget(self, action: #selector(refresh(sender:)), for: .valueChanged)
self.table.refreshControl = refreshControl
table.addSubview(refreshControl)
fetchData()
}
private func fetchData() {
let feedParser = FeedParser()
feedParser.parseFeed(url: url) { (rssItems) in
self.rssItems = rssItems
DispatchQueue.main.async {
self.table.reloadData()
}
}
}
End refreshing once you get data and set or add refresh control
#objc func refresh (sender: UIRefreshControl) {
fetchData()
}
override func viewDidLoad() {
super.viewDidLoad()
refreshControl.addTarget(self, action: #selector(refresh(sender:)), for: .valueChanged)
self.table.refreshControl = refreshControl
// table.addSubview(refreshControl)
fetchData()
}
private func fetchData() {
let feedParser = FeedParser()
feedParser.parseFeed(url: url) { (rssItems) in
self.rssItems = rssItems // as you are reloading table here
DispatchQueue.main.async {
refreshControl.endRefreshing()
// self.table.reloadData()
}
}
}

UIRefreshControl's tintColor can't be initially set

I'm currently struggling with a problem that I couldn't handle and I wasn't able to find any answer or workarounds.
The problem is that I want to change color for the refreshControl to .white for example, I call the beginRefreshingManually() function in every viewcontroller's viewDidLoad() but the initial color is still black. When trying to manually pull to refresh the Refreshcontrol becomes .white
Here's some of the code I've wrote:
extension UIScrollView {
func setupRefreshControl(withTintColor tintColor: UIColor = JDColorConstants.UIScrollView.refreshControlWhiteColor.color,
completion: #escaping (() -> Void)) {
let refreshControl = UIRefreshControl()
refreshControl.tintColor = tintColor
refreshControl.addAction(forEvent: .valueChanged, closure: {
DispatchQueue.main.asyncAfter(deadline: .now() + JDDurationConstants.shortAnimationDuration) {
completion()
}
})
self.refreshControl = refreshControl
}
}
extension UIRefreshControl {
func beginRefreshingManually() {
DispatchQueue.main.async { [weak self] in
guard let scrollView = UIApplication.shared.topViewController()?.getScrollView() else { return }
let constant = UIApplication.shared.getTopHeightOfSafeArea() +
(UIApplication.shared.topViewController()?.navigationController?.navigationBar.bounds.height ?? 0) +
(self?.frame.height ?? 0)
scrollView.setContentOffset(CGPoint(x: 0, y: -constant), animated: true)
self?.sendActions(for: .valueChanged)
self?.beginRefreshing()
}
}
}
Did you try call manually beginRefreshingManuall method in viewDidLoad ?
override func viewDidLoad() {
super.viewDidLoad()
//it will react as if you were being renewed with your hand
yourUIRefreshControl.beginRefreshingManually()
}

Swift UIRefreshControl to UICollectionView inside UIScrollView

I have UICollectioview inside UIScrollView and I want to add UIRefreshControl, but it don't work
private let refreshControl = UIRefreshControl()
override func viewDidLoad() {
super.viewDidLoad()
self.refreshControl.addTarget(self, action: #selector(didPullToRefresh(_:)), for: .valueChanged)
self.folderCollectionView.alwaysBounceVertical = true
self.folderCollectionView.bounces = true
self.refreshControl.tintColor = UIColor.black
self.folderCollectionView.refreshControl = refreshControl
}
#objc
private func didPullToRefresh(_ sender: Any) {
print("123")
getFolder(update: true)
refreshControl.endRefreshing()
}
In getFolder I get data and reload collection
When I run collection don't have pull to update and don't work
What have I done wrong
Add UIRefreshControl to the UICollectionView using this way, see the following code, I hope it helps you.
// Add Refresh Control to Collection View
if #available(iOS 10.0, *) {
folderCollectionView.refreshControl = refreshControl
} else {
folderCollectionView.addSubview(refreshControl)
}