How to update RSS data in UITableView? - swift

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()
}
}
}

Related

Beginner question on passing data between view controllers

I am trying to recreate the Notes app in iOS. I have created an initial View Controller which is just a table view. A user can go to a Detail View Controller to compose a new note with a Title and Body section. When they click Done, I want to manipulate the tableView with note's details.
I am struggling saving the details of what the user entered to use on my initial view controller.
Here's my Notes class which defines the notes data:
class Notes: Codable {
var titleText: String?
var bodyText: String?
}
Here is the Detail View controller where a user can input Note details:
class DetailViewController: UIViewController {
#IBOutlet var noteTitle: UITextField!
#IBOutlet var noteBody: UITextView!
var noteDetails: Notes?
var noteArray = [Notes]()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(updateNote))
noteTitle.borderStyle = .none
}
#objc func updateNote() {
noteDetails?.titleText = noteTitle.text
noteDetails?.bodyText = noteBody.text
noteArray.append(noteDetails!) // This is nil
// not sure if this is the right way to send the details over
// let vc = ViewController()
// vc.noteArray.append(noteDetails!)
if let vc = storyboard?.instantiateViewController(identifier: "Main") {
navigationController?.pushViewController(vc, animated: true)
}
}
}
I also have an array on my initial view controller as well. I think I need this one to store note data to display in the tableView (and maybe don't need the one on my Detail View controller?). The tableView is obviously not completely implemented yet.
class ViewController: UITableViewController {
var noteArray = [Notes]()
override func viewDidLoad() {
super.viewDidLoad()
print(noteArray)
self.navigationItem.setHidesBackButton(true, animated: true)
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .compose, target: self, action: #selector(composeNote))
}
#objc func composeNote() {
if let dvc = storyboard?.instantiateViewController(identifier: "Detail") as? DetailViewController {
navigationController?.pushViewController(dvc, animated: true)
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
noteArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
return cell
}
Just using Delegate:
First create delegate protocol with a func to send back note to your viewController
protocol DetailViewControllerDelegate: AnyObject {
func newNoteDidAdded(_ newNote: Note)
}
Next add the delegate variable to DetailViewController, and call func noteDataDidUpdate to send data back to viewController
class DetailViewController: UIViewController {
weak var delegate: DetailViewControllerDelegate?
#objc func updateNote() {
....
delegate?.newNoteDidAdded(newNote)
}
}
finally, set delegate variable to viewController and implement this in ViewController
class ViewController: UIViewController {
....
#objc func composeNote() {
if let dvc = storyboard?.instantiateViewController(identifier: "Detail") as? DetailViewController {
dvc.delegate = self
navigationController?.pushViewController(dvc, animated: true)
}
}
}
extension ViewController: DetailViewControllerDelegate {
func newNoteDidAdded(_ newNote: Note) {
// do some thing with your new note
}
}

TableView doesn't refresh when created programmatically

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()
}

How to fix "Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value" while using tableview.reloaddata

I'm creating an app where I use a NSOpenPanel, linked to a File --> Open... menu. When the user select a .txt file, the program reads it, add the values to an existing array of strings and then SHOULD reload the data in the TableView. But when I call the tableview.reloaddata(), at run time, it give me the following error: Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value.
Here the code of my AppDelegate.swift file:
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
let vc = ViewController(nibName: "ViewController", bundle: nil)
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}
#IBAction func openUnFichier(_ sender: NSMenuItem) {
let fichierPanel: NSOpenPanel = NSOpenPanel()
fichierPanel.allowsMultipleSelection = false
fichierPanel.canChooseFiles = true
fichierPanel.canChooseDirectories = false
fichierPanel.allowedFileTypes = ["txt"]
let response = fichierPanel.runModal()
if response == NSApplication.ModalResponse.OK{
guard let selectedURL = fichierPanel.url else{return}
do{
var fullDocument = try String(contentsOf: selectedURL, encoding: String.Encoding.utf8)
print(type(of: fullDocument))
var lines : [String] = fullDocument.components(separatedBy: "\n" as String)
for line in lines {
vc.test_data.append(line)
print(type(of: vc.test_data))
}
} catch let error as NSError{
print("Erreur!!!!!!! \(error)")
}
vc.tableView.reloadData() //IT CRASHES HERE
}else {
}
}
}
And here is the code of my ViewController.swift:
import Cocoa
import WebKit
class ViewController: NSViewController, NSTableViewDataSource{
public var test_data = ["https://www.youtube.com/watch?v=0AQFQMeOAig", "https://www.youtube.com/watch?v=domoD_w3uFw"]
var test_text = ""
var nextUrl = ""
//func
func refresh(){
tableView.reloadData()
}
#IBAction func plus(_ sender: NSButton) {
if urlInput.stringValue == "" {
} else {
test_text = urlInput.stringValue
test_data.append(test_text)
urlInput.stringValue = ""
tableView.reloadData()
// fonction du bouton +
}
}
#IBAction func nextLien(_ sender: NSButton) {
if test_data == [] {
} else {
nextUrl=test_data[0]
var monUrl = URL(string: nextUrl)
var maRequete = URLRequest(url: monUrl!)
view_web.load(maRequete)
test_data.remove(at: 0)
tableView.reloadData()
//fonction du bouton pour le prochain lien
}
}
func numberOfRows(in tableView: NSTableView) -> Int {
return test_data.count
}
func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
return test_data[row]
}
//var
#IBOutlet weak var urlInput: NSTextField!
#IBOutlet weak var view_web: WKWebView!
#IBOutlet weak var tableView: NSTableView!
//func vitale
override func viewDidLoad() {
super.viewDidLoad()
let myURL = URL(string: "https://www.youtube.com")
let myRequest = URLRequest(url: myURL!)
view_web.configuration.preferences.plugInsEnabled = true
view_web.load(myRequest)
// Do any additional setup after loading the view.
}
}
What I don't understand, it's that the viewtable.reloaddata() works fine in the ViewController.swift file, but the same instruction doesn't work when I try to do it in the AppDelegate file.
I do have check that my array of strings (test_data) is not empty. (It contains 4 elements after I load and extract the data of a ".txt" file that I had created myself.)
I would like to know how could I fix this error so the data is shown in my TableView after I parse my txt file.
Thank you very much.
There is a simple solution: Move the IBAction to the view controller.
In Interface Builder disconnect the IBAction from AppDelegate.
Move the openUnFichier method to ViewController (delete it in AppDelegate).
In Interface Builder connect the menu item to First Responder (the red cube) and choose openUnFichier in the list. The first class in the responder chain which implements the method will execute it.

Error while assigning self to tableview datasource

This is the error Xcode outputs
Unexpectedly found nil while unwrapping an Optional value
I have a viewcontroller that has a tableview and a few buttons; the buttons allow me to insert or remove data. It seems that when I click on Add (which brings up a new viewcontroller via segue as a sheet) the app crashes with the error above. Clicking on remove doesn't have this affect. So it has to do with something regarding the new viewcontroller as a guess. The console doesn't go further into the error other than printing out (lldb)
Here's my code
override func viewDidLoad() {
super.viewDidLoad()
alarmTableView.dataSource = self //error occurs here
alarmTableView.delegate = self //if i remove the above line if will occur here too.
}
My Viewcontroller which the above viewDidLoad func is embedded lists the protocols I need
class ViewController: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
#IBOutlet weak var addAlarm: NSButton!
#IBOutlet weak var resetDataButton: NSButton!
#IBOutlet var alarmArrayController: NSArrayController!
#IBOutlet weak var alarmTableView: NSTableView!
#IBOutlet weak var deleteAll: NSButton!
#objc let moc: NSManagedObjectContext
required init?(coder: NSCoder) {
self.moc = CoreDataHandler.getContext()
super.init(coder: coder)
}
override func prepare(for segue: NSStoryboardSegue, sender: Any?) {
let destinationController = segue.destinationController as! AddAlarmViewController
//pass data to next controller here
}
#IBAction func deleteAllAction(_ sender: Any) {
if (alarmTableView.selectedRow >= 0) {
if (CoreDataHandler.deleteAllObjectsInEntity(entityName: "Alarm")) {
//remove from nsarray controller
for object in alarmArrayController.arrangedObjects as! [Alarm] {
print(object)
alarmArrayController.removeObject(object)
}
alarmTableView.reloadData()
}
}
else {
printInfo(str: "There are no alarms to delete")
}
}
/* Response to the remove alarm button - It removes a selected alarm object from the table */
#IBAction func resetDataAction(_ sender: Any) {
if (alarmTableView.selectedRow >= 0) {
let selectedAlarm = self.alarmArrayController.selectedObjects.first as! Alarm
alarmArrayController.remove(atArrangedObjectIndex: alarmTableView.selectedRow)
CoreDataHandler.deleteObjectInEntity(entityName: "Alarm", obj: selectedAlarm)
alarmTableView.reloadData()
}
else {
//will need a warning or play a sound.
printInfo(str: "Please select an alarm")
}
}
override func viewDidLoad() {
super.viewDidLoad()
printInfo(str: "viewdidload")
print(alarmTableView)
if (alarmTableView != nil) {
printInfo(str: "AlarmTableView Is initialised")
alarmTableView.dataSource = self
alarmTableView.delegate = self
}
else {
printInfo(str: "AlarmTableView is not initialised")
}
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
func printInfo(str: String) {
print("ViewController: \(str)")
}
func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat {
return 100.0
}
}
class AddAlarmViewController: ViewController {
#IBOutlet weak var closeButton: NSButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
printClassInfo(str: "viewDidLoad")
CoreDataHandler.saveTestData()
}
#IBAction func closeButtonAction(_ sender: Any) {
self.dismissViewController(self)
}
func printClassInfo(str: String) {
print("AddAlarmViewController \(str)")
}
}
If I remove the lines where the error occurs the app run fine. But I want to override the delegate and datasource and use the functions to further customise the table. I'm also using Cocoa Bindings.
Why am I getting this error?
Update
I haven't solved it yet, but i placed a couple of print statements in my viewDidLoad function. It seems that when the app is first loaded, the table view is initialised. But after when I clicked on the Add button, the table view is then set to nil for some odd reason, as if another table view has been initialised. However the data is still visible
Problem:
class AddAlarmViewController: ViewController {
//...
override func viewDidLoad() {
super.viewDidLoad()
//...
}
}
Your AddAlarmViewController is a subclass of ViewController instead of NSViewController.
In AddAlarmViewController's viewDidLoad you call super.viewDidLoad() which basically calls ViewController's viewDidLoad.
But... in this case ViewController is a new instance as the super class of AddAlarmViewController and none of it's properties are initialized.
Whatever it be, it's probably not what you want.
Solution:
class AddAlarmViewController: NSViewController {
//... rest as it is
}

Delegates is not working?

here is my protocol definition.
protocol ActivityIndicatorDelegate: class {
func showIndicator()
func hideIndicator()
func barcodeError()
func categoryError()
func descError()
func reasonError()
func costError()
}
Then in my Custom cell class I create weak reference and I call delegate function
class ProductTableViewCell: UITableViewCell {
weak var indicatorDelegate: ActivityIndicatorDelegate?
#IBAction func stockUpdate(_ sender: Any) {
indicatorDelegate?.categoryError()
}
}
Then in my UITableViewController class
class ProductTableViewController:
UITableViewController,ActivityIndicatorDelegate{
override func viewDidLoad() {
super.viewDidLoad()
let cellDelegate = ProductTableViewCell()
cellDelegate.indicatorDelegate = self
}
func categoryError() {
//self.showAlert(alertTitle: "Error!", alertMessage: "Category Should not be empty")
print("Error")
}
}
I have written all these in a single file. What I'm doing wrong here? Can some one help me to solve this. Thanks in advance.
You should not set the delegate in viewDidLoad. This will only set the delegate of the cell that you just created, instead of all the cells in the table view.
You should do this in celForRowAtIndexPath:
let cell = tableView.dequeue...
// configure the cell...
cell.indicatorDelegate = self