I have created a file in Swift called infoModel. This is used to pass info from one ViewController to another, and the info is written in a label. This works perfectly, but I want to be able to delete that info, or at least remove it from the label.
I have tried to set label = nil, but the info pops back in when I change to a different viewController and back.
How should I go about to remove the data?
Code infoModel:
import UIKit
class infoModel: NSObject {
var info: String = ""
var info2: String = ""
var info3: String = ""
var info4: String = ""
func currentInfo() -> String {
//return a string with the current info
return info + "" + info2 + "" + info3 + "" + info4 + ""
}
}
And in view controller:
class collectViewController: UIViewController {
var myInfo = infoModel()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
infoLabel.text = myInfo.currentInfo()
}
}
It´s the infoLabel I want to remove from.
*Edit: The app is collecting info from users. The way that it does that, is simply that the user is pressing a/some button/buttons that represents the suitable values for that specific user. The information is then displayed in a different View Controller. If the user presses the wrong button, I want them to be able to delete/remove the information and start over. The removebutton is in the same View controller that the infoLabel is.
When all the information is correct, the user then pushes another button that writes to Firebase Database.
So my goal is that the user is able to delete/remove the info, and start over.
Don't set initial value to myInfo, make it an optional:
var myInfo: infoModel?
and
infoLabel.text = myInfo?.currentInfo()
Now if there is no model, nothing will be set.
Also, you can automatically update the label using:
var myInfo: infoModel? {
didSet {
_ = self.view // load the view if not loaded
infoLabel.text = myInfo?.currentInfo()
}
}
Now just setting myInfo = nil will clear the label.
you need to use the model as a container of your data and changes.
when the user presses the various buttons, you need to update the model, and when the user clicks the remove button, you need to update the model to reflect that state.
Instead of setting the entire object to nil, set the inside variables to nil, so that the model object always exist. You will need to do more unwrapping.
var info: String? = nil
var info2: String? = nil
var info3: String? = nil
var info4: String? = nil
If you don't want to do that, you will need to check the nullability of your model and create a new one when it is nil (as Sultan explained in the other response)
(I could also suggest you use a struct and not a class)
You then need to redraw your UI when the model is updated.
I think in your simple case even running setNeedsDisplay on the view of the controller should do the trick
I actually solved the issue with not being able to re add data to the label just by doing this:
myInfo?.info = ""
myInfo?.info2 = ""
myInfo?.info3 = ""
myInfo?.info4 = ""
Related
I'm stuck on something which should be trivial, using SwiftUI. I am pulling some data back from my API, and simply want to show each item on my view with ForEach.
I have the following function:
#State var workoutVideoList: GetWorkoutVideoListResponse!
func GetWorkoutVideoList() {
loadingWorkoutVideoList = true
PtPodsApiService.GetWorkoutList(firebaseToken: firebaseAuthToken) { (workoutListResult) -> () in
DispatchQueue.main.async() {
workoutVideoList = workoutListResult
loadingWorkoutVideoList = false
}
}
}
I then want to show another view and pass this object into it:
if workoutVideoList != nil {
BookAppointmentView(workoutVideoList: workoutVideoList)
}
else {
Text("Nope")
}
For whatever reason, my main thread sees workoutVideoList as nil, even though it's been assigned. I thought using the dispatcher to assign it would solve the problem, but no joy!
Any idea what I'm doing wrong here?
Thanks
Give #State a default value #State var workoutVideoList: GetWorkoutVideoListResponse = false and use onAppear to call GetWorkoutVideoList() but ideally you will eventually get this working in a ViewModel.
I have a view whose ViewModel configures the view. The user can update the ViewModel and this object is later passed onto another view which will reflect the state of the preview view. Here is an example.
struct ViewSettings {
var btn1Selected: Bool
var btn2Selected: Bool
var btn3Selected: Bool
init() {
btn1Selected = true
btn2Selected = true
btn3Selected = true
}
}
class ViewOne: UIView {
var settings: ViewSettings
init(settings: ViewSettings) {
self.settings = settings
}
func configureView() {
btn1.isSelected = settings.btn1Selected
btn2.isSelected = settings.btn2Selected
btn3.isSelected = settings.btn3Selected
}
#objc func tapBtn1(_ sender: UIButton) {
btn1.isSelected = btn1.isSelected.toggle()
settings.btn1Selected.toggle()
}
#objc func tapBtn2(_ sender: UIButton) {
btn2.isSelected = btn2.isSelected.toggle()
settings.btn2Selected.toggle()
}
#objc func tapBtn3(_ sender: UIButton) {
btn3.isSelected = btn3.isSelected.toggle()
settings.btn3Selected.toggle()
}
}
This setting is later used inside another view. If btn1 is selected in ViewOne and when ViewTwo uses that setting, btn1 in ViewTwo is selected too.
Question:
I'm doing a direct mutation on the settings to achieve this. Is there a better design pattern that would let me arrive at the same solution?
your viewModel is struct that means its a value type, even when you think you are passing the same viewModel to other views, in reality you send a different copy of viewModel not the same instance.
So when you mutate value in view1 and pass the copy of it to view2, if view2 changes the value again, your viewModel in view1 will not be updated, its not passed by reference its passed by value.
so If your question was that I am mutating value directly will it cause side effects no because they are different copies, but if you want the changes to be reflected in all the views that holds this viewModel then it won't.
Finally answering your question
Question: I'm doing a direct mutation on the settings to achieve this.
Is there a better design pattern that would let me arrive at the same
solution?
At very first, sharing viewModel across view itself is arguable. Should this be done or not, as such the whole idea is opinion based. Some might say its fine some might say its not!
In general, I have seen people sharing viewModel across views but I personally refrain from doing so, but that doesn't mean that either of the approach is the only right way. Its best left to developers judgement.
Few clarifications:
struct ViewSettings {
var btn1Selected: Bool
var btn2Selected: Bool
var btn3Selected: Bool
init() {
btn1Selected = true
btn2Selected = true
btn3Selected = true
}
}
This looks more like DataModel and less of ViewModel isn't it? Its just a data container, no business logic, no data presentation/modification mechanisms nothing in it, its a plain data model. You are sharing DataModel across view and you wanna mutate and pass it on to next view I think its fine to go ahead with it.
In the app I am developing I need to update 2 view controllers when a button is tapped. I managed to update the view controller which contain the button, but i could not update the content of the other view controller.
I tried to do so by a couple of ways: first, I created a delegate variable (using a protocol) in the view controller of the button and set its value to the other view controller:
var delegate: WeatherServiceDelegateForcast? = ViewController2()
as ViewController2 is the view controller I am trying to update using the delegate.
the protocol:
protocol WeatherServiceDelegateForcast{
func SetForcast(forcast: ForcastInfo)
}
The SetForcast func in viewController2
func SetForcast(forcast: ForcastInfo) {
self.day1Label.text = ....
}
the problem is that when executing the function SetForcast, the IBOutlet of day1label is nil and as a result it cannot be updated. I suppose that is happens because the view controller has not yet been loaded, but i could not figure out a way to load it.
secondly, I tried to use segue (without delegate) to pass the data to viewcontroller2. That worked but only when I used type show for the segue and in my app it is critical that it won't open. When i tried to use segue without opening viewcontroller2 (by type custom) it once again did not work and could not identify the IBOutlets.
Any suggestions how to pass the data to viewcontroller2 without opening it?
edit:
this is the code i used:
when the button in viewcontroller1 is tapped it takes the data from textfield and call the function from its property weather service (that works).
self.weatherService.GetForcast(textField.text!)
GetWeather gets the weather of a city, creates a variable from type forecast and then:
if self.delegate2 != nil
{
dispatch_async(dispatch_get_main_queue(), {
self.delegate2?.SetForecast(forecast)
})
}
as delegate2 is the property of weatherService that refers to viewcontroller2 and this is how it is defined in the beginning of weatherService class:
var delegate2: WeatherServiceDelegateForecast? = ViewController2()
and WeatherServiceForcast is the protocol:
protocol WeatherServiceDelegateForcast
{
func SetForecast(forecast: ForecastInfo)
}
then, according to the debugger, it executes SetForecast of viewcontroller2 with the right forecast variable:
func SetForcast(forcast: ForcastInfo) {
self.day1 = String(round(forcast.day[0])) + "-" + String(round(forcast.night[0]))
self.day2 = String(round(forcast.day[1])) + "-" + String(round(forcast.night[1]))
self.day3 = String(round(forcast.day[2])) + "-" + String(round(forcast.night[2]))
self.day4 = String(round(forcast.day[3])) + "-" + String(round(forcast.night[3]))
self.day5 = String(round(forcast.day[4])) + "-" + String(round(forcast.night[4]))
self.day6 = String(round(forcast.day[5])) + "-" + String(round(forcast.night[5]))
self.day7 = String(round(forcast.day[6])) + "-" + String(round(forcast.night[6]))
}
as day1-7 are variables in viewController2 class from which i want to update the labels of viewController2 afterwards, and this is how these variables are defined:
var day1 = String()
var day2 = String()
var day3 = String()
var day4 = String()
var day5 = String()
var day6 = String()
var day7 = String()
in viewDidLoad i put the code to update the labels into the strings of the variables day1-7 but it seems it firstly calls that function and only then it calls set forecast and updates the variables.
override func viewDidLoad() {
// Do any additional setup after loading the view.
self.day1Label.text = self.day1
self.day2Label.text = self.day2
self.day3Label.text = self.day3
self.day4Label.text = self.day4
self.day5Label.text = self.day5
self.day6Label.text = self.day6
self.day7Label.text = self.day7
}
Why not update the content of your viewController2 in viewWillAppear or viewDidLoad? A view controller won't load its view and subviews until it needs to display them on screen.
Therefore, Views of your viewController2 is not loaded if you are not going to present it, that's why you got nil and you can't access it.
A clean way to achieve what you want is:
Have a separate property in viewController2 to store the data
Set content of the property in viewController1
Then update the view of viewController2 in viewDidLoad or viewWillAppear according to the property you just set.
Edit
Try change this:
if self.delegate2 != nil
{
dispatch_async(dispatch_get_main_queue(), {
self.delegate2?.SetForecast(forecast)
})
}
to
if self.delegate2 != nil
{
self.delegate2?.SetForecast(forecast)
}
Code inside a async block will be executed asynchronously , so viewWillAppear or viewDidload may be called before these properties are set.
That's why these values are "". Also, no need to use async block here, what you are doing in SetForcast is just set values of properties, they are finished almost immediately.
You simply can't do this.
If the ViewController2 is not loaded it won't never take the data.
You first have to initialize it, then you can give it the data, without displaying it.
You simply have to do an "alloc-init" to load it inside a variable. The you give the data.
In my app each user sets up a profile page and all this info is stored on Parse. When the current user hits a button it segues to a tableViewController that displays in cells all the other users on the app. In each cell there is a button which the user presses to view the full profile page of the user that they have selected. I have a VC set up to hold all the info I just don't know how to display the particular user that the current user has selected into it. How do I single out the chosen users information? I have read the Parse documentation on relational queries but I can't seem to get my head around it. https://parse.com/docs/ios/guide#queries-relational-queries
So in finer detail basically all my users are displaying on a VC. It has name and profile picture, when the user clicks on the info button beside a certain user it should open a new screen and display all of this users information stored on Parse.
I have tried creating a static outside the class
struct Data { var OtherName:String! var id:String! }
And then in my ViewDidLoad:
let query = PFQuery(className: "_User")
// otherQuery.orderByDescending("createdAt")
query.whereKey("username", notEqualTo:PFUser.currentUser()!.username!)
query.findObjectsInBackgroundWithBlock { (users: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// success
print(users!.count)
for user in users! {
self.imageFiles.append(user["image"] as! PFFile)
self.instrumentText.append(user["instrument"] as! String)
self.nameText.append(user["name"] as! String)
self.ageText.append(user["age"] as! String)
// self.locationText.append(user["location"] as! PFGeoPoint)
var singleData = Data()
singleData.id = user.objectId
singleData.OtherName = user["name"] as! String
print("\(singleData)")
} // users
Then on the VC that the info button segues to I am trying to call it and upload each label with just the user that was selected.
self.userName.text = "" as String
But the "" is staying blank.
Then I was thinking maybe if I add that when the more info button is pressed that the user it was pressed on gets chosen and is added to a chosen string on Parse and then I just have to call the user from the chosen string when I am on the chosen users profile page and call the info that way and then when the user clicks the back button the user is no longer chosen. It just seems like I would be adding and taking away a lot of users to "chosen" in Parse is there a better way?
I am able to display all the users info but how do I just display a chosen user from Parse without using the objectId because at the top of each cell it will return a different user here each time depending on settings previously set on a different VC such as only show users in a certain age bracket etc. I know I must set an Id of some sort on button touched but I am at a complete loss. The only online help out there that I can find is set for other languages eg. https://parse.com/docs/js/guide#users-querying>
So I guess my question is :
1.Does anyone know a tutorial they could point me towards.
2.Does anyone know how I even get started and I can go from there myself.
3.Can anyone help?
*Swift and Parse newbie little to no experience in other languages.
New code in firstVC:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "userProfileDetailsSegue" {
if let indexPath = resultsPageTableView.indexPathForSelectedRow {
let controller = (segue.destinationViewController) as! UserProfileDetailsViewController
***error here: UserProfileDetailsViewController.userToShowDetail = indexPath.row
//line above is the one I can't get to work it needs to equal to the cell selected?
}
}
}
New Code in destinationVC:
var userToShowDetail: PFUser? {
didSet {
self.configureView()
}
}
func configureView() {
// Update the user interface for the detail item.
if let userToShowDetail: PFUser = self.userToShowDetail {
if let label = self.userName {
nameText.append(userToShowDetail["name"] as! String)
// self.nameText.append(user["name"] as! String)
}
}
}
You're going to need to do a couple of things for this to work. First, in the view controller that you're using to show another user's information, you need to set a property like var userToShowDetail: PFUser?. Then in your view with all of the users of your app, you need to create a segue to bring them from the current view to the detail view. Assuming you're using a TableView, you can connect just one prototype cell to the detail view you're trying to navigate. Then you'll need to override prepareForSegue and set the new view controller's user equal to the user at the row that was selected. I don't know how you're storing your objects of user's of your app, but you should probably be able to do something like this.
override fun prepareForSegue(segue: UIStoryboardSegue) {
if segue.identifer == "segueToShowDetailInfo" {
//get the index path for the row that was selected
let indexPath = self.tableView.indexPathForSelectedRow()
//get the PFUser object for that particular row
let userToShow = self.appUsers[indexPath.row]
//create your new view controller
let newVc = segue.destinationViewController as! DestinationVC
//assign the new vc's property to your object
newVc.userToShowDetail = userToShow
}
}
But in your query, you probably shouldn't have a separate array of the the different parts of the user's data. You should just make one array, call it var appUsers = [PFUser]() and then when you get a user back in your query, just say:
for user in users! {
self.appUsers.append(user) as! PFUser
}
There, now you have a whole array of objects that holds a reference to every user. When someone is using your app, and selects a cell, then you can perform the prepare for segue method as above.
Note that I also noticed you have a didSet on your variable in your new view controller. Calling a function to update the UI after this variable is set will not work, and may crash, because the view controller doesn't have a window yet.
I have a setting in my app that I am having issues retaining the state of. Basically, the UISwitch is on by default, however when updating my app to a newer version, it seems to switch off. I figured out that if the user has opened the settings menu in version 1.0, then this isn't an issue, however if they have never changed a setting, or even opened the settings menu, then it's a problem. Therefore, it must be an issue somewhere in viewWillAppear() but I can't figure out what it is. The code I have for storing the switches state seems really overcomplicated.
This is the code I have in my settings menu:
#IBAction func dupOffOnSwitch(sender: AnyObject) {
if dupSwitch.on == true {
autoAdjust = true
println(autoAdjust)
} else {
autoAdjust = false
println(autoAdjust)
}
NSUserDefaults.standardUserDefaults().setBool(autoAdjust, forKey: "autoAdjustSettings")
}
override func viewWillAppear(animated: Bool) {
autoAdjust = NSUserDefaults.standardUserDefaults().boolForKey("autoAdjustSettings")
if autoAdjust == true {
dupSwitch.on = true
} else {
dupSwitch.on = false
}
}
if userReturnedAuto == false {
dupSwitch.on = true
themeSwitch.on = false
userReturnedAuto = true
NSUserDefaults.standardUserDefaults().setBool(userReturnedAuto, forKey: "userReturnedAuto")
NSUserDefaults.standardUserDefaults().setBool(userReturnedAuto, forKey: "autoAdjustSettings")
}
I am declaring the bool 'autoAdjust' in a different view controller as a global variable. In that same view controller in viewWillAppear() I have this code:
autoAdjust = NSUserDefaults.standardUserDefaults().boolForKey("autoAdjustSettings")
Can anyone suggest a better way of storing the switches state while also having it on by default when the app is first launched? Or a fix to my current solution.
NSUserDefaults.standardUserDefaults().boolForKey("key") defaults to false. Therefore, as in your case, if the user never sets the key, then it will be false and the switch will default to off.
Instead, rename the key and use it as the negative. So instead, call it autoAdjustSettingsOff which will default to false and the switch will default to on. Don't forget to switch around the true/false settings in your conditional blocks.
Not sure if this is over kill(or just stupid). But why not use CoreData to save all the users settings. Then when the app becomes active(in your appDelegate) you can set the NSUserDefaults from these stored values. Just a thought :)
For shortness I will only make an example for one switch, you can then fill out and add the rest. I will also assume you will be updating the NSUserDefaults when the switch changes state. We will only update/save the core data when the app goes to in-active and load settings once when app becomes active.
Also if anyone sees something I missed or could be done better please let me know and I will update the answer.
First off you need to make sure you import the CodeData libraries into your view controller:
import CoreData
class fooBarViewController: UIViewController {
You need to create and entity in core data: lets assume it is called "Settings"
then once you have created the entity, you need to add some attributes. Press the "+" symbol.
Now add the attribute and set the name to "dupSwitch" and type to "Boolean". Add an attribute for each setting you need to store.
Next: You need to create a new swift file. Type NSObject. name it something like "dataObjects".
Inside you you will use the following code: (first remove all current code - make it blank)
import UIKit
import CoreData
#objc(settingObj)
class settingObj: NSManagedObject {
#NSManaged var dupSwitch:NSNumber //do this for each setting
}
Next you need to go back to your CoreData dataModel and do the following.
1. click on "Default" under the section "Configuration"
2. You will then get a list of all your "entities" select "Settings"
3. click on class and edit the field to "settingObj"
Now that you have you CoreData set up. You can now save/edit/delete data as need be.
For example when you need to load all saved user settings when app goes will become active.
in your AppDelegate.swift file: import the CoreData libraries.
import CoreData
then add this function if it is not already present
func applicationWillEnterForeground(application: UIApplication) {
}
inside "applicationWillEnterForeground" you will load any settings that have been stored in CoreData
var results:[settingObj] = []
let appDel:AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let context:NSManagedObjectContext = appDel.managedObjectContext!
let req = NSFetchRequest(entityName: "Settings")
let tapResults = context.executeFetchRequest(req, error: nil)
if let presentResults = tapResults {
if let castedResults = presentResults as? [settingObj] {
for item in castedResults {
let dupSwitch = item.dupSwitch
let settingSwitch = item.whateverElseYouNeedExample
//now based on this we can set the NSDefault
NSUserDefaults.standardUserDefaults().setBool(dupSwitch, forKey: "autoAdjustSettings")
NSUserDefaults.standardUserDefaults().setBool(settingSwitch, forKey: "whateverElseYouNeedExample")
}
}
}
Now when you want to save the users settings. I would would just it when app goes in-active.
First we will check if the settings have already been saved. If so, then we will just perform
an update. If not, we will save a new entry into yuor CoreData Entity.
add the following func to your appDelegate(if not already there):
func applicationDidEnterBackground(application: UIApplication) {
}
EDIT I HAVE TO GO TO WORK WILL COMPLETE REST OF ANWSER WHEN I GET BACK HOME. REALLY SORRY.