How do i refresh a TableView after some data will changed? - arrays

I want to change the list of users in only one class, called Network. And i don't understand how to make a TableView update after the userList has changed. I'll show you an example and detailed question in code below.
// Network.swift
class Network {
var userList: [User] = []
// Next functions may change userList array
// For example, the data came from the server, and update the userList with new data
}
// App delegate
class AppDelegate: UIResponder, UIApplicationDelegate {
var network: Network = Network()
..
}
// File TableViewController.swift
class TableViewController: UITableViewController {
…
var userList: [User] = [] // Here I want to have a full copy of the array from Network class
override func viewDidLoad() {
super.viewDidLoad()
let appDelegate = UIApplication.shared.delegate as! AppDelegate
self.userList = appDelegate.network.userList // Just copy an array
// And I want that after each update appDelegate.network.userList I updated the table, how to do it better?
self.tableView.reloadData()
}
}

As #JDM mentioned in comment your architecture is messed.
Try to do this delegation using protocols:
// Network.swift
protocol UsersUpdatedProtocol {
func usersListUpdated(list: [User])
}
class Network {
var userList: [User] = [] {
didSet {
delegate?.usersListUpdated(list: userList)
}
}
var delegate: UsersUpdatedProtocol?
init(delegate d: UsersUpdatedProtocol) {
super.init()
delegate = d
}
}
// File TableViewController.swift
class TableViewController: UITableViewController, UsersUpdatedProtocol {
var userList: [User] = []
override func viewDidLoad() {
super.viewDidLoad()
let _ = Network(delegate: self)
}
func usersListUpdated(list: [User]) {
self.userList = list
self.tableView.reloadData()
}
}

You could use a notification. Whenever the userlist gets updated, post a notification like this:
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "UserlistUpdate"), object: nil)
Then, in viewDidLoad add:
NotificationCenter.default.addObserver(self, selector: #selector(TableViewController.reloadData), name: NSNotification.Name(rawValue: "UserlistUpdate"), object: nil)
P.S. regarding your architecture so far, I would make the TableViewController hold a variable for Network rather than hold its own user array. Then, in AppDelegate:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
let network = Network()
// Access the TableViewController and set its network variable
let tableViewController = window!.rootViewController as! TableViewController
tableViewController.network = network

Related

Swift - Why is Class Instance Appended to Array Deinitialized?

I'm having memory problems with the Swift code below.
In GameScene's sceneDidLoad(), I add a GKEntity to the array "entities," but in GameScene's update(), the array "entities" is empty. Shouldn't the reference to "entity" be preserved in "entities"? Why is "entity" being deinitialized at the end of sceneDidLoad()?
import GameplayKit
class GraphicsComponent: GKComponent {
override func update(deltaTime: TimeInterval) {
// ...
}
}
class GameScene: SKScene {
var entities = [GKEntity]()
override func sceneDidLoad() {
let entity = GKEntity()
entity.addComponent(GraphicsComponent())
entities.append(entity)
}
override func update(_ currentTime: TimeInterval) {
// ...
for entity in entities {
entity.update(deltaTime: dt)
}
}
}
I was expecting the "entity" instance to be preserved because a reference to it was added to the "entities" array. If that reference remained, then why was the instance destroyed?
EDIT:
I found that if I move the code in sceneDidLoad() to didMove(), everything works as expected. I still didn't know why, so I looked around in the boilerplate that Xcode puts in GameViewController and found where sceneDidLoad() and didMove() are each called. I found that if I add an entity to the scene.entities array in sceneDidLoad(), the scene.entities array will be empty when the code steps into the if statement that begins with if let scene = GKScene(fileNamed: "GameScene"). Is the if let creating a temporary GKScene?
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// This "if let" will call sceneDidLoad(), which appends a new entity to scene.entities.
if let scene = GKScene(fileNamed: "GameScene") {
// scene.entities.count = 0
if let sceneNode = scene.rootNode as! GameScene? {
sceneNode.entities = scene.entities
sceneNode.graphs = scene.graphs
sceneNode.scaleMode = .aspectFill
if let view = self.view as! SKView? {
view.presentScene(sceneNode) // This calls didMove()
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
}
}
}

Firestore instantiate objects with data recover Swift 5.0

I get all the data from my snapshot and create an object list with the data.
My problem: I can't return a list to use my objects in other code functions.
I tried to browse my list to create using my snapshot to implement a new list of objects declared above in my code.
class ViewController: UIViewController {
lazy var usersCollection = Firestore.firestore().collection("ship")
var ships: [MyShip] = []
override func viewDidLoad() {
super.viewDidLoad()
getUsers()
print(ships.count)
}
The getData function:
func getUsers() {
usersCollection.getDocuments { (snapshot, _) in
//let documents = snapshot!.documents
// try! documents.forEach { document in
//let myUser: MyUser = try document.decoded()
//print(myUser)
//}
let myShip: [MyShip] = try! snapshot!.decoded()
// myShip.forEach({print($0)})
for elt in myShip {
print(elt)
self.ships.append(elt)
}
print(self.ships[1].nlloyds)
}
}
result console
Result in the console:
- my list is not filled return 0
- I print the objects well and I print them well
- I print the ships object[1].nloyds = 555 well in the function
Your print(ships.count) call in viewDidLoad is printing an empty array because the .getDocuments() method is asynchronous. Try writing getUsers as a closure like this:
func getUsers(completion: #escaping ([MyShip]) -> Void) {
usersCollection.getDocuments { (snapshot, _) in
let myShip: [MyShip] = try! snapshot!.decoded()
completion(myShip)
}
}
and then use it in the viewDidLoad method like this:
override func viewDidLoad() {
super.viewDidLoad()
getUsers() { shipsFound in
self.ships = shipsFound
print(self.ships.count)
}
}

pass an array from appDelegate to a Viewcontroller using a protocol without segues

I need to pass an array from AppDelegate to a viewcontroller using protocols. I am new to this concept. Please help me with some code. I need to pass dataArr of strings to another viewcontroller and display it on tableview
guard let message = note(fromRegionIdentifier: region.identifier) else { return }
window?.rootViewController?.showAlert(withTitle: nil, message: "you have entered " + message)
if (dataArr.count <= 5){
dataArr.append(message)
}
let userdefaults = UserDefaults.standard
userdefaults.set(dataArr, forKey: "message")
}
I just need to save those alerts to userdefaults and display them on a tableview i just tried this but it is only showing single string on tableview
let savedstring = UserDefaults.standard.array(forKey: "message")
cell?.cordinateLabel.text = savedstring?[indexPath.row] as? String
return cell!
Using Protocol
First create protocol
protocol MyDelegate {
public yourMethod(param: String);
}
In your ViewController you need to extend it from Protocol, and set it to AppDelegate
class YourViewController: MyDelegate {
// Your Other methods
override func viewDidLoad() {
super.viewDidLoad()
// set your delegate to Appdelegate
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.yourDelegate = self;
}
func yourMethod(param: String) {
// Do your stuff
}
}
Now Finally in AppDelegate declare protocol object and call yourMethod through its reference.
class AppDelegate: UIApplicationDelegate {
public yourDelegate: MyDelegate!;
}
Now You can call your method anywhere in your AppDelegate like
yourDelegate.yourMethod(params);
Using NotificationCenter
The easiest way is to do so using NotificationCenter.
First you need to add extension to Notification.Name anywhere in your App. Like
extension Notification.Name { static let mynotification = Notification.Name("mynotification") }
In your view controller viewDidLoad method add
NotificationCenter.default.addObserver(self, selector: #selector(yourMethod), name: NSNotification.Name.mynotification, object: nil)
Then in your ViewController add a method which will be called when notification fired
func yourMethod(){
//// do something
}
Now in your App delegate or even anywhere from app, you can call viewController's method by sending notification Like
NotificationCenter.default.post(name: NSNotification.Name.mynotification, object: nil)

How can I show my whole array in DetailView

I am working on a ToDo List application. Currently, the ToDo list shows in Table View as a list and the Detail View shows only the first item, a completed switch and the date completed. I want my Detail View to show all of the items and dates completed, not just the first item.
My app delegate looks like this:
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
let splitViewController = self.window!.rootViewController as! UISplitViewController
let navigationController = splitViewController.viewControllers.first as! UINavigationController
let masterViewController = navigationController.topViewController as! MasterViewController
let detailViewController = splitViewController.viewControllers.last as! DetailViewController
let firstItem = masterViewController.listManager.getFirstItem()
detailViewController.toDoItem = firstItem
return true
}
Now, I've tried to change firstItem to toDoItem to show my whole array but I am getting an error:
Cannot assign value of type '[ToDoItem]' to type 'ToDoItem!'
Also, what to I create in Detail View to accommodate an every changing array?
This is my current Detail View:
import UIKit
class DetailViewController: UIViewController {
#IBOutlet weak var descriptionLabel: UILabel!
#IBOutlet weak var dateAddedLabel: UILabel!
#IBOutlet weak var completedSwitch: UISwitch!
var toDoItem: ToDoItem! {
didSet(newItem) {
self.refreshUI()
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
refreshUI()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func refreshUI() {
if(toDoItem == nil) {
return
}
descriptionLabel?.text = toDoItem.itemDescription
completedSwitch?.setOn(toDoItem.completed, animated: true)
dateAddedLabel?.text = toDoItem.getDateAdded()
}

AVAudioPlayer using array to queue audio files - Swift

I am looking for a way to simply play audio files one after another.
AVAudioPlayer using an array seems to be the best solution. In fact, I was able to play the first element of the array using Sneak's recommendations found on this page : Here.
But I don't understand where and how to write the second call to AVAudioPlayer in order to play the second file?
The "audioPlayerDidFinishPlaying" function is not reacting. Why?
Thanks for watching.
import Cocoa
import AVFoundation
var action = AVAudioPlayer()
let path = Bundle.main.path(forResource: "test.aif", ofType:nil)!
let url = URL(fileURLWithPath: path)
let path2 = Bundle.main.path(forResource: "test2.aif", ofType:nil)!
let url2 = URL(fileURLWithPath: path2)
let array1 = NSMutableArray(array: [url, url2])
class ViewController: NSViewController
{
#IBOutlet weak var LanceStop: NSButton!
override func viewDidLoad()
{
super.viewDidLoad()
do
{
action = try AVAudioPlayer(contentsOf: array1[0] as! URL)
action.numberOfLoops = 0
action.prepareToPlay()
action.volume = 1
}catch{print("error")}
}
...
#IBAction func Lancer(_ sender: NSButton)
{
if action.isPlaying == true
{
action.stop()
action.currentTime = 0.0
LanceStop.title = "Lancer"
}
else
{
action.play()
LanceStop.title = "Stopper"
}
}
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool)
{
if flag == true
{
LanceStop.title = "Lancer"
}
}
}
But I don't understand where and how to write the second call to
AVAudioPlayer in order to play the second file?
So in order to play the second file, you need to write one method where you will need to initialize the audioplayer and invoke the same method inside audioplayer delegate method audioPlayerDidFinishPlaying like this:-
func playAudioFile(_ index: Int) {
do
{
action = try AVAudioPlayer(contentsOf: array1[index] as! URL)
action.numberOfLoops = 0
action.prepareToPlay()
action.volume = 1
} catch{print("error")
}
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
//This delegate method will called once it finished playing audio file.
//Here you can invoke your second file. You can declare counter variable
//and increment it based on your file and stop playing your file acordingly.
counter = counter + 1
playAudioFile(counter)
}
Note:- Set Audioplayer delegate to your ViewController in order to get invoke
audioPlayerDidFinishPlaying method like this.
action.delegate = self
Changed Code...
class ViewController: NSViewController, AVAudioPlayerDelegate
{
#IBOutlet weak var LanceStop: NSButton!
override func viewDidLoad()
{
super.viewDidLoad()
}
override var representedObject: Any?
{
didSet
{
// Update the view, if already loaded.
}
}
func playAudioFile(_ index: Int)
{
do
{
action = try AVAudioPlayer(contentsOf: array1[index] as! URL)
action.delegate = self
action.numberOfLoops = 0
action.prepareToPlay()
action.volume = 1
action.play()
}
catch{print("error")}
}
#IBAction func Lancer(_ sender: NSButton)
{
playAudioFile(0)
}
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool)
{
if flag == true
{
playAudioFile(1)
}
}
}

Resources