Store label information into history table | Swift | Xcode
If someone can help me with this. So basically what this application does is, it uses azure cognitive services to convert a picture into text and display it in the second screen when analyze is pressed, I want this information to be stored in the third view as a history. Can anyone help in achieving this? Very new in swift, so trying to figure out stuff. Thanks in advance.
Screenshot of the app main storyboard, to clarify what I am asking:
So all you really need is a data structure that allows you to store your data and allow it to be passed between view controllers. The simple approach would be to have:
struct PictureDetail {
photo: UIImage
text: String
}
Depending on the volume/size of images, you'd probably better actually holding the photos as files and just keeping the file path in the struct and have a calculated property that retrieves it from disk:
struct PictureDetail {
photoPath: URL
text: String
photo: UIImage {
// load photo from URL & return
}
}
The you need some form of collection to hold all your data and to allow it to be injected into wherever it is needed
class DataModel {
var pictureData: [PictureDetail] = []
func addPictureDetail(picture: UIImage, text: String) {
//save picture to disk and obtain URL
pictureData.append(PictureDetail(url: url, text: text)
}
Then instantiate the data model in your initial view controller
let dataModel = DataModel()
And then have a DataModel property in the other VCs, and inject the value during your prepare(for segue: IUSegue) method.
Related
I'm trying to get data from an URL (1st network call), then make a 2nd network call to fetch image from the url in the first response, and present on tableview in swift.
The json from 1st network call contains 3 strings:
struct Item: Codable {
var title: String?
var image_url: String?
var content: String
}
What I need to do is, after json decoding and get this [Item] array, I also need to make network call for each item in it so I can also get the UIImage for each of them and present title, content and Image on each cell.
My question is, where is the best place to make the network call (in MVVM pattern)?
My first thought is in the TableViewCell:
func configCell(item: Item) {
titleLabel.text = item.title
descriptionLable.text = item.content
// fetch image
service.fetchImage(with: item.image_url) { result in
switch result {
case .success(let image):
DispatchQueue.main.async { [weak self] in
if let image = image {
self?.iconView.isHidden = false
}
}
case .failure(let error):
print(error.localizedDescription)
return
}
}
}
However, I'm not sure if this the right way/place to do so because it might cause wrong image attach to each cell. Also it couples network layer into view layer.
So my 2nd thought is, create a second model in the viewModel and make network call in it:
struct ImageItem: Codable {
var title: String?
var image_url: String?
var content: String
var image: Data?
init(with item: Item) {
self.title = item.title
self.content = item.content
ContentService().fetchImage(with: item.image_url) { result in
switch result {
case .success(let image):
self.image = image?.pngData()
case .failure(let error):
print(error.localizedDescription)
}
}
}
But that doesn't feel right either. Also seems like I can't make network call in struct initializer.
Could anyone help or give me advice about where to put this 2nd layer network call for fetching the image?
If those images are lightweight, you can do it directly inside cellForRow(at:) method of UITableViewDataSource. If using a library/pod such as Kingfisher - it's just 1 line that takes care of of, plus another one to import the library:
import Kingfisher
...
cell.iconView.kf.setImage(with: viewModel.image_url)
Otherwise, you may have to take care yourself of caching, not downloading images unless really needed etc.
As long as the image download/configuration is done in UIViewController and not inside the Custom Cell, it should be all good. But still make sure you use some protocol and separate Repository class implementing that protocol and containing the image download functionality, because the View Controller should be more like a Mediator and not be loaded with too much code such as networking and business logic (VCs should act more like view, and generally speaking it's not their job to get the remote image for your custom cells, but for simpler apps that should be ok, unless you want to over-engineer things :) ).
Some people argue that View Models should be light weight and therefore structs, while others make them classes and add lots of functionality such as interacting with external service. It all depends of what variation of MVVM you are using. Personally I prefer to simply keep a URL property in the View Model and implement the "getRemoteImage(url:)" part outside the custom cell, but I've seen people adding UIImage properties directly to the View Models or Data type properties and having some converters injected that transform Data -> UIImage.
You can actually have a Repository and inject the Networking code to the View Model, and then add an Observer<UIImage?> inside the View Model and bind the corresponding cells' images to those properties via closures so the cell automatically updates itself on successful download (if still visible) or next time it appears on screen... Since each cell would have a corresponding view model - each individual view model can keep track if the image for IndexPath was already dowloaded or not.
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
This question already has answers here:
Passing data between view controllers
(45 answers)
Closed 3 years ago.
I am currently working on an app and have problems with posting events.
I collect data in a sequence of several views (7 screens) and would like to store the data finally at once (or show all the data in the final view).
These data are, for example, location info, user info, event image, comments, event category, ...
I know how to store the data in the database/storage (firebase) if I collect the data in one or two views.
But in my use case I have seven views and I could not find any elegant method.
What's the best way to do that with Xcode 10?
You can use struct as below code. Make all required variable for all screen in this struct (like string, image etc..). And you can access this from any ViewController.
struct InputDetails {
static var details: InputDetails = InputDetails()
var city: String = ""
var lat: String = ""
var long: String = ""
}
Now to add value in this
InputDetails.details.city = textfiels.text
Now to access first screen value in last screen
print(InputDetails.details.city)
And once your API call or above struct usage is over, make sure to reset all details like below.
InputDetails.details = InputDetails()
There are several ways for passing data between View Controllers. For example you could use an instance property or a segue or the delegation method.
I recommend you study this article which paints a complete picture of the different methods and how to apply them:
How To: Pass Data Between View Controllers In Swift
Edit:
Upon examining the picture in your question I figured that using a segue would be the most appropriate solution here. As it seems from the picture you enter data in one View Controller, pass that onto the second View Controller and finally you upload all the data to Firebase.
I assume that you use storyboards (if not then consult the link above for other methods.) In this example below you will pass a string from one VC to another.
Step 1:
Add a segue between two view controllers. Storyboard -> press ctrl and click on VC one and drag your mouse -> you will see a blue arrow, drag that to VC two and release -> select manual segue: show -> click on the segue and give it an identifier
Step 2:
In VC two, make a string variable:
class SecondViewController: UIViewController {
var stringToPass: String = ""
override func viewDidLoad() {
super.viewDidLoad()
print(stringToPass)
}
Step 3:
In VC one, enter the following:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? SecondViewController {
vc.stringToPass = "This is the string we pass between two VC"
}
}
Step 4:
Then whenever you want to go to the SecondViewController perform the segue like this:
performSegue(withIdentifier: "identifierYouEntered", sender: self)
I have Swift object with about 20 Properties. In the app, there is a screen to get the user input and create the above swift object from the user entered value. Right now, if the user clicks the back button, all the user entered data will lose. So I want to alert the user to save the details if he/she has made any changes. How do we identify if the user has made any changes to the properties. Is it possible to use KVO in this case as we have too many properties?
What you need is a data model to hold the information in that particular screen, and then compare it with the original data when leaving the screen.
For the sake of simplicity, let's assume your screen has 2 text fields. One holds a name and another the age of a person.
struct Person: Equatable {
var name: String
var age: Int
}
When you first open this screen, the model will have the default values. Create a copy of this model and whenever the user makes any changes to the values on the screen, update the copy.
class YourViewController: UIViewController {
// Populate these 2 values when creating your view controller
var person: Person!
var personCopy: Person!
.
.
.
// You need to add this target to your text fields
#objc func textFieldDidChange(_ textField: UITextField) {
switch textField {
case personTextField:
personCopy.name = personTextField.text!
case ageTextField:
personCopy.age = Int(ageTextField.text!)!
default:
// Handle other text fields here or write separate cases for them
}
func dismissView() {
if person == personCopy {
// Dismiss your view
} else {
// Show alert
}
}
}
If the user presses the back button, all you need to do is compare these 2 models and check if they are the same. If they are the same, you can go back, if not, prompt an alert asking the user to save changes or discard.
I think KVO would be overkill here. Use KVO only for distant objects in your app.
Here you have the UITextFields in your viewController and you must have reference to the user object anyway.
Easier is: On the back button press you would check all text properties of your UITextField objects to the (existing) values of your user object. If one of them has changed then present the alert.
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!)