I have been playing around with a lot of stuff involving arrays and scrollviews. I have mostly stayed within the confines of view controllers, so usually i'll grab data from firebase, add it to an array, and then send it to the tableview or collectionview. What I'm trying to do now is actually navigate between viewcontrollers (or multiple copies of the same view controller) and applying the array items to each view controller.
For example I want to be able to grab some photos from firebase, put them in an array of url strings or whatever. Then I want to put a photo on the background of a view controller. Then when I push the over button it goes navigates to the next view controller and puts the next photo as the background there, etc.
I understand there are probably multiple ways to do this and I was wondering what is the most efficient way? Do I just put an array in a Global class and access that from all the view controllers? Or do I just keep the array in the first view controller, then as I navigate, keep sending it to the next view controller over and over? Also there will be a LOT of items and objects and arrays here so that's why I'm looking for efficiency. Thanks in advance to anyone able to help with this, and I hope I explained it well enough!
This is a very simple way of adding and retrieving String value from a struct, here you are saving the image url string as a value in a dictionary and it's key is going to be the ViewController name.
struct SavedData {
static private var imagesDictionary: [String: String] = [:]
static func image(for viewController: UIViewController) -> String? {
return imagesDictionary["\(type(of: viewController))"]
}
static func add(image name: String, for viewController: UIViewController) {
self.imagesDictionary["\(type(of: viewController))"] = name
}
}
saving a value is very simple, if you're saving the data in a viewController and you want a specific image to be saved for that viewController you can use self
SavedData.add(image: "img1.png", for: self)
And if you want to save an image for a different viewController, do it like this.
SavedData.add(image: "img2.png", for: SecondViewController())
Retrieving the image is also very simple, you should call this method in the viewController that you want to assign the image to.
let savedImage = SavedData.image(for: self)
print(savedImage!)
Related
I have Tab Bar Controller, where I have few view controllers, but I want to pass array of values (workoutNames) to another view in my Tab Bar Controller. I wonder what's best option to do this and I've decided to use way of passing data with property. But when I try to retrieve data I get empty array. I could also use firestore to retrieve data in my Second View Controller, but It lasts too long, so I decided to passing data between views than retrieve data from firestore every time.
First View Controller
class HomeTableViewController: UIViewController
// I need to pass this array to another view
var workoutsName: [String] = []
...
func sendDataToCalendar() {
// IN THIS FUNCTION I RETRIEVE DATA FROM FIRESTORE AND UPDATE ARRAY WITH VALUES
// After all I print this array to check if everything is correct, and my data is here
print("\(workoutsName)")
}
Here is my Second View Controller when I want to use array from First View Controller
class CalendarViewController: UIViewController {
var arrayOfTitles = [String]()
.
.
.
func getArrayFromHome() {
let homeVC = HomeTableViewController()
homeVC.workoutsName = arrayOfTitles
// NOW I PRINT TO CHECK IF DATA EXISTS
print("\(arrayofTitles)"
}
And its empty, so data didn't pass.
Maybe it's not the best way to pass data, but main idea of this is that I will need to use this array in few view controllers. These controllers won't be connected by segue, so I can't use prepareforSegue. It's not one to one relationship (in future I will need this array in few controllers), so I shouldn't use delegate. I don't think that notification will be ok too, so I think that it's best option to pass data by property. But maybe I am wrong, so please correct me.
The reason why it doesn't work is that you instantiate a new HomeTableViewController with empty data.
If this data will be used on lots of place, why not save it locally? user default seems like it fit your needs.
func sendDataToCalendar() {
UserDefaults.standard.set(workoutsName, forKey: "workoutsName")
}
Then you can read it later on
func getWorkoutNameArray() {
let workoutName = UserDefaults.standard.object(forKey: "workoutsName") as? [String]
}
In your getArrayFromHome() function you are not accessing HomeTableViewController instance where you got the data but creating a new instance. That's why the array is empty. You end up with 2 instances of HomeTableViewController, one in use with the correct array and the dummy one created in the function with an empty array.
Would be better if you pass the data in the same place where you have a reference to CalendarViewController.
Let's say that you are creating and presenting CalendarViewController in your HomeTableViewController like:
let calendarViewController = CalendarViewController()
calendarViewController.arrayOfTitles = workoutNames
// Here is the presentation or push of calendarViewController
It will be useful for you to read this SO question
I have two Views:
UITableViewController (View A)
UIViewController (View B)
I was wondering, if it's possible to load and setup the table from View B and then segue to View A, when the loading is done. I need this, since the Table View loads Data from Core Data and that takes some time; I would then show a Loading Animation or something. I have a function called loadData() in View A, which fetches all Elements from Core Data and then calls tableView.reloadData().
Does anyone know, how I could implement this? Or should I somehow show the loading View directly from View A with a SubView or something?
Remember to not think about the specifics but instead, think generally:
You want to move from one VC to another and you have some data that needs to be fetched asynchronically. Let's assume you can't know how long it will take.
My suggestion is to contain all data fetching related to a VC inside that VC itself (or services/facades related to it). So basically you should present the UITableViewController and then have it fetch the data while showing skeleton-cells/spinner/etc.
You want to have separation of concerns which means you don't want your ViewController to handle data related to another view controller.
Think about the following use-case: if you have code to fetch data in the previous VC, before presenting the TVC, what happens when you need to re-fetch the data or refresh something? You will have to duplicate the code in both the VC and the TVC.
That's why it's suggested to keep data fetching inside the view controller that needs it.
If, for some reason, you still want to have your answer for this specific question:
You can have the initial VC create the TVC, but not present it yet, call its methods to fetch the data, and have it send a callback (closure/delegate/etc) when it's done fetching. When the fetching is done, present the TVC.
Here is a quick example:
class MyTableVC: UITableViewController {
private var myData: [Int] = []
public func fetchData(completion: () -> Void) {
//Fetch data asyncly
myData = [1, 2 ,3]
completion()
}
}
class MyVC: ViewController {
private func loadTableVC() {
let tableVC = MyTableVC()
tableVC.fetchData { [weak self] in
self?.present(tableVC, animated: true, completion: nil)
}
}
}
Again, I wouldn't use this due to having tight coupling between the 2 view controllers, but it's always up to you to decide how to design your code.
I have a View-Hierarchy like this:
UIViewController (SingleEventViewController)
UIScrollView (EventScrollView)
UIView (contentView)
3xUITableView (SurePeopleTV, MaybePeopleTV, NopePeopleTV (all inherited from the same UITableView)), & all other UI-Elements
The SingleEventViewController stores one Event (passed within the initializer). (All Events are stored in Core-Data).
The three UITableViews are there for displaying the users which are participating (or not or maybe) at the Event. My question is, what are the possibilities to fill the tableViews with the data and what would you recommend in which situation.
Currently I have a property parentVC: SingleEventViewController in all Subviews and get the data like this:
override func loadUsers() {
//class SurePeopleTV
guard let parentController = parentVC else { return }
users = (parentController.thisEvent.eventSureParticipants?.allObjects as! [User])
finishedLoading = true
super.loadUsers()
}
.
func applyDefaultValues() {
//class EventScrollView
guard let parent = parentVC else { return }
titleLabel.text = parent.eventName
}
I'm new to programming but I got a feeling that I should not create a parentVC reference in all of my classes.
An object should not (ideally) know about its parent - if it does they are "tightly coupled". If you change the object's parent, your code may break. In your case, your parent object must have a thisEvent property.
You want your objects to be "loosely coupled", so the object doesn't know about a specific parent object.
In Swift, the usual ways to pass information "back up the chain" is to use the delegate design pattern… https://developer.apple.com/documentation/swift/cocoa_design_patterns or to use closures.
See also https://www.andrewcbancroft.com/2015/04/08/how-delegation-works-a-swift-developer-guide/ for info on delegation
First of all, if you create a reference to the parent ViewController make sure it is weak, otherwise you can run into memory management issues.
Edit: As Ashley Mills said, delegates the way to handle this
The recommended way to pass data between ViewControllers is using something like this
Every time a segue is performed from the view controller this function is in this function is called. This code first checks what identifier the segue has, and if it is the one that you want, you can access a reference to the next view controller and pass data to it.
In my storyboard I got:
UIView -> UITabBarController -> UINavigationController -> UITableView
Now I want to pass an object from UIView into UITableview. I do get the object to the TabBarController from the prepare for segue func, but from there I kind of get lost.
How to identify what segue you have on the itemlist from the TabBarController?
Could somebody give some example code for the UITabBar and Navigation controller to pass the data?
Phillip is right.
You can do it as following:
class Model {
static let shared = Model()
var data: String // or anything else
}
in UIView:
Model.shared.data = "some data"
in UITableView
let data = Model.shared.data
//do smth with data...
Anton is suggesting the Singleton pattern. It is important to understand what it is when you decide to use it has both its benefits and potential pitfalls. https://thatthinginswift.com/singletons/ is a place to start reading up.
There are ways to just pass an object from one view to the other and that is useful knowledge to know. Both TabBarVC's and NavigationVC's have their viewControllers property which allows you to access an array of their child vc's. You can use this to pass information to specific child vc's. Depending on your needs this may be more appropriate than creating a singleton.
For example:
let childVC = tabBarVC.viewControllers[0] as! MyCustomVCClass
childVC.inheretedObject = objectIWantToSend
This would pass an object to the vc that ocupies the first tab of a tab bar vc.
I have created a struct and made an array of that type. The struct consists of two variable:
struct notesarray
{
var prioritycolor : UIColor
var note : String
}
In my secondVC which houses a collectionViewController, I have made an array of type notesarray. I am sending values for prioritycolor and note from firstVC.
I will be setting up CoreData later on, for now I just want this to work in simplest of manners. I am appending data from firstVC to this array like so:
#objc func handleCheckButton()
{
print("Added")
let secondVC = AddedNotesCollectionViewController()
secondVC.allnotes.append(notesarray(prioritycolor: taskTextView.backgroundColor!, note: taskTextView.text))
print(secondVC.allnotes.count)
taskTextView.text = nil
}
allnotes is the name of the array found in secondVC.
For testing purposes I am printing secondVC.allnotes.count but I am just getting '1' in console no matter how many time I add elements to the array.
I have also tested this by placing print(allnotes.count) under viewDidAppear func in secondVC so that whenever I go to secondVC it gives me count of the elements in the array but it also shows '0' every time.
I don't know what I am doing wrong here. Please help me!
Thats because you end up getting a new instance of AddedNotesCollectionViewController every time you press the button.
let secondVC = AddedNotesCollectionViewController()
And new instance is initiated with an empty array and you add one element to it by calling
secondVC.allnotes.append(notesarray(prioritycolor: taskTextView.backgroundColor!, note: taskTextView.text))
Hence count is always one. iOS is correct there my friend :)
What you need:
If second VC is already loaded either by pushing a it on to navigation stack of FirstVC or if its presented then get the reference to the presented/pushed VC rather than creating a new one every time. There are many answers in SO which explains how to access the pushed/modally presented VC :)
If you are about to present/push the SecondVC, as you mentioned in the comments you can always make use of prepareForSegue to pass the data.
If in case your AddedNotesCollectionViewController is never presented then rather consider creating singleton instance of notesArray which you will share between multiple VCs.
Hope it helps