Cannot take values from other view controller Swift - ios

I want to take user settings details from this view controller and read these details to the previous view controller. I have tried many different ways, but I cannot take values until I visit this view controller
I have tried first method from this page Pass Data Tutorial
This method is also not working. I think it is very simple, but I cannot figure out the right way to do it.
class SetConvViewController: UIViewController {
var engS = "engS"
#IBOutlet weak var swithEnglish: UISwitch!
override func viewDidLoad() {
super.viewDidLoad()
if let eng2 = defaults.value(forKey: engS)
{
swithEnglish.isOn = eng2 as! Bool
}
}
let defaults = UserDefaults.standard
#IBAction func switchEng(_ sender: UISwitch) {
defaults.set(sender.isOn, forKey: engS)
}
}

If I understand you correctly from this part - „but I cannot take values until I visit this view controller” - your problem lies with the fact, that until you visit your settings, there is no value for them in UserDefaults.
If you are reading them using getObject(forKey:) method, I’d recommend you to switch to using getBool(forKey:), since it will return false even if the value has not been set yet for that key ( docs )
Anyhow, if you want to set some default/initial values you can do so in your didFinishLaunching method in AppDelegate :
if UserDefaults.standard.object(forKey: „engS”) == nil {
// the value has not been set yet, assign a default value
}
I’ve also noticed in your code that you used value(forKey:) - you should not do that on UserDefaults - this is an excellent answer as to why - What is the difference between object(forKey:) and value(forKey:) in UserDefaults?.
On a side note, if you are using a class from iOS SDK for the first time, I highly recommend looking through its docs - they are well written and will provide you with general understanding as to what is possible.

I would recommend you to store this kind of data as a static field in some object to be able to read it from any place. e.g.
class AppController{
static var userDefaults = UserDefaults.standard
}
and then you can save it in your SetConvViewController like
#IBAction func switchEng(_ sender: UISwitch) {
AppController.userDefaults.set(sender.isOn, forKey: engS)
}
and after that you can just read it from any other view controller just by calling
AppController.userDefaults

Using segues you can set to any destination whether it be next vc or previous:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "PreviousVC" {
if let prevVC = segue.destination as? PreviousViewController {
//Your previous vc should have your storage variable.
prevVC.value = self.value
}
}
If you're presenting the view controller:
Destination vc:
//If using storyboard...
let destVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "DestinationViewController") as! DestinationViewController
destVC.value = self.value
self.present(destVC, animated: true, completion: nil)
Previous vc:
weak var prevVC = self.presentingViewController as? PreviousViewController
if let prevVC = prevVC {
prevVC.value = self.value
}

Related

Transferring an object from a Realm database to another UITabBar controller

Tell me, please, I'm trying to solve the problem of transferring an instance of a class to another controller using the Realm database.
I have a main controller that stores objects according to the model the following data:
class Route: Object {
#objc dynamic var routeImage: Data?
#objc dynamic var routeName: String?
#objc dynamic var numberOfPersons = 0.0
#objc dynamic var dateOfDeparture: String?
#objc dynamic var dateOfArrival: String?
let placeToVisit = List<Place>()
let person = List<Person>()
}
In the controller to which I need to transfer this data, I created
var currentRoute: Route!
In the Storyboard, I specified the identifier "showDetail" from the controller cell to the UITabBar, and in the main controller, I created a method:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetail" {
guard let indexPath = tableView.indexPathForSelectedRow else {return}
let newPlaceVC = segue.destination as! InformationViewController
newPlaceVC.currentRoute = routes[indexPath.row]
}
}
Error I got:
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKitCore/UIView.h> may also be helpful.
Could not cast value of type 'UITabBarController' (0x111ed8b10) to 'Organizer_Tourist.InformationViewController' (0x108dd0a70).
2019-10-07 14:30:35.626853+0800 Organizer Tourist[5467:2618892] Could not cast value of type 'UITabBarController' (0x111ed8b10) to 'Organizer_Tourist.InformationViewController' (0x108dd0a70).
(lldb)
But it is not valid, the application crashes by tap on the cell. I suppose this would work if there was not a tabBar, but a regular table, view controllers. I was looking for solutions and all I came across was implementation through singleton. Now I have a lot of questions, but will this really be the right decision? People say this violates the "modularity" of the application and carries its own problems. The question is how is this done through singleton? What to consider, where to start? Which method is worth editing?
Error said what is happening:
Could not cast value of type 'UITabBarController'
You are trying to cast segue.destination to typo InformationViewController which is not.
If you embed your InformationViewController in UITabBarController so you need to cast to your UITabBarController rather than InformationViewController.
Try something like this:
if segue.identifier == "showDetail" {
guard let indexPath = tableView.indexPathForSelectedRow else { return }
let tabBarController = segue.destination as! UITabBarController
UserSelectedRoute.shared.selectedRoute = routes[indexPath.row]
}
If you want to pass current selected route to InformationViewController you can create singleton object which will be hold current route
final class UserSelectedRoute {
private init() { }
static var shared = UserSelectedRoute()
var selectedRoute: Route?
}
And then in your InformationViewController you can have something like:
var currentRoute = UserSelectedRoute.shared.selectedRoute
Hope this will help you!

How do I pass my array data coming from api to another ViewController?

I'm new in iOS.
I'm trying to pass my array data to another view controller through prepareforsegue method.
And this api is called inside the #IBAction func ButtonTapped( ).
class FirstVc {
var location = [Any]()
self.clientrequest.request(url: "http://api.details.in/api/users/alllocation", method: .GET, completion: {
res , err in
let json = try! JSONSerialization.data(withJSONObject: res , options: .prettyPrinted)
let decode = try! JSONDecoder().decode(SelectLocation.self, from:json)
self.location = decode.states
//I'm getting all locations here
print(self.location)
})
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.destination is SecondVc
{
let vc = segue.destination as? SecondVc
//here I'm assigning the variable and while I'm printing location here then also I'm getting the value
vc?.mineSpillere2 = self.location
}
}
class SecondVc{
#IBOutlet weak var selectState: UILabel!
var mineSpillere2 = [Any]()
override func viewDidLoad() {
//While I'm trying to print mineSpillere2 here I', getting []
selectState.text = mineSpillere2 as? String
}
}
One problem might be that you don't actually assign the variable (because vc may be nil). Also you may want to use segueIdentifiers, especially if you have more than one. To make sure that you do so, you may want to use something like this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? SecondVc //, segue.identifier = "YOUR_SEGUE_ID"
{
vc.mineSpillere2 = self.location
} else { print("SecondVc couldn't be initialised!") }
}
First, the problem is that web service request is not executed asynchronously but synchronously. In other words, your segue is already performed before decode.states is assigned to the location variable.
The solution is to call performSegue function in the completion block:
self.clientrequest.request(url: "http://api.details.in/api/users/alllocation", method: .GET, completion: {
res , err in
let json = try! JSONSerialization.data(withJSONObject: res , options: .prettyPrinted)
let decode = try! JSONDecoder().decode(SelectLocation.self, from:json)
self.location = decode.states
self.performSegue(identifier: "your identifier",
sender: nil)
//I'm getting all locations here
print(self.location)
})
Second,
Make sure your segue is assigned from VC1 to VC2 not Button to VC2.
There are several way to pass this in another ViewController. Good way is make a model class and pass model object to another vc .
Or might be another short and easy solution is make Global variable. So you can access it from any ViewController class. like
var myGloblArray= [Any]()
//add your result array in this array , you can access it from second VC Directly.
class FirstVc {
...
}

Passing data between view controllers through segue

I have a MapViewController with a prepareForSegue(_:sender:)method, which I intend to use to send data to LandmarkTableViewController, and is called when a button is pressed.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let destinationvc = segue.destinationViewController
if let landmarkvc = destinationvc as? LandmarkTableViewController {
if let identifier = segue.identifier {
let library = Landmark(name: "Run Run Shaw Library", properties: ["Chinese Kanji", "Gray", "Green Plants"])
let bank = Landmark(name: "Hang Seng Bank", properties: ["Chinese Kanji", "Green"])
switch identifier {
case "showLibrary" : landmarkvc.passedLandmark = library // pass data to LandmarkTableViewController
case "showBank" : landmarkvc.passedLandmark = bank // pass data to LandmarkTableViewController
default : break
}
}
}
}
The LandmarkTableViewController is properly set up to display the String array properties, with one String on each row. So what I intend to do is pass the appropriate data for the table to properties according to which button was pressed, and let LandmarkTableViewController display the corresponding properties.
class LandmarkTableViewController: UITableViewController {
var properties = [String]()
var passedLandmark = Landmark(name: "temp", properties: ["temp"]) // initially set to default value
override func viewDidLoad() {
super.viewDidLoad()
loadSampleProperties()
}
func loadSampleProperties() {
self.properties = passedLandmark!.properties
}
// other methods....
}
class Landmark {
var name: String
var properties: [String]
init?(name: String, properties: [String]) {
self.name = name
self.properties = properties
// Initialization should fail if there is no name or if there is no property.
if name.isEmpty || properties.isEmpty {
return nil
}
}
However, when I run the code, only temp is displayed in the table view. I've been stuck on this for a long time now, so any help is much appreciated!
Edit: loadData() inside of viewDidLoad() is changed to the correct loadSampleProperties(). I made an error while posting the code to the question.
I think this should solve your problem if not double check your identifiers
and you can make sure to data passing with adding print(passedLandmark) to viewDidLoad() or breakpoint to make sure you getting the data
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let destinationvc = segue.destinationViewController
if let landmarkvc = destinationvc as? LandmarkTableViewController {
if segue.identifier == "showLibrary" {
let library = Landmark(name: "Run Run Shaw Library", properties: ["Chinese Kanji", "Gray", "Green Plants"])
landmarkvc.passedLandmark = library
}
if segue.identifier == "showBank" {
let bank = Landmark(name: "Hang Seng Bank", properties: ["Chinese Kanji", "Green"])
landmarkvc.passedLandmark = bank
}
}
}
Hope this will helps
Code is missing from your quote, so I can't be sure, but I assume your loadData() method is the one that reloads the table view data with Landmark you've passed in prepareForSegue. If that is the case:
viewDidLoad() is called before prepareForSegue, so that all the views and elements of the destinationViewController are loaded and ready to use. Thus, in your case, the table view is loaded with your "temp" data and nothing makes it reload when you set the proper one.
You have two options:
You could call loadData()/reloadData() in viewWillAppear for example, which is called after prepareForSegue(). Bare in mind that viewWillAppear will possibly be called again in some other navigation.
Otherwise, you could instantiate and present/push the new controller in your parent view controller, instead of using the segue.

NSUserDefaults key becomes nil when sent between view controllers

I need to send a username between two view controllers so that the second view controller knows who to send a message to. I have tried prepareForSegue, however I have found that the variable passed cannot be dynamically altered. I decided to use NSUserDefaults, which worked very well for the length of my development process. Today, it stopped working. I do not think I deleted anything or made an changes, but nevertheless NSUserDefaults is no longer reliably carrying the value between the two view controllers. Every once in a while (maybe 20% of the time?) the value will be correctly passed. The rest of the time, nothing comes through.Code:
Set key:
func chooseFriend(sender: UIButton) {
let requestIndex = sender.tag
let friendChosen = self.friends.objectAtIndex(requestIndex) as! String
NSUserDefaults.standardUserDefaults().setValue("thisisatest", forKey: "testKey")
NSUserDefaults.standardUserDefaults().synchronize()
self.performSegueWithIdentifier("toChat", sender: self)
}
Note: In the viewDidLoad I set testKey = ""
Retrieve key on new view controller:
override func viewDidLoad() {
super.viewDidLoad()
let theKey = NSUserDefaults.standardUserDefaults().valueForKey("testKey")
print("The Key: \(theKey)")
refreshTable()
let swipe: UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: "dismissKeyboard")
swipe.direction = UISwipeGestureRecognizerDirection.Down
self.view.addGestureRecognizer(swipe)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide:"), name: UIKeyboardWillHideNotification, object: nil)
testLabel.text = ""
self.messages.addObject("Loading......")
}
Every time, the default comes up empty: The Key: Optional()I can successfully print the default after setting it, however it disappears once I am segued to the next view controller...If anyone else has experienced this problem please let me know.Thanks
Randy's code:
func chooseFriend(sender: UIButton) {
let requestIndex = sender.tag
let friendChosen = self.friends.objectAtIndex(requestIndex) as! String
// Instantiate the second view controller via t's identifier in the storyboard
if let secondViewController = self.storyboard?.instantiateViewControllerWithIdentifier("ChatVC") as? chatViewController {
// Set the chosen friend
secondViewController.friendChosen = friendChosen
self.presentViewController(secondViewController, animated: true, completion: nil)
}
}
Added this to destinationviewcontroller:
var friendChosen: String!
The methods for NSUserDefaults are setObject:forKey: and objectForKey:, not setValue:forKey: (Or look at the special methods for specific object types, like setBool:forKey: or stringForKey: (I don't think there's a custom set method for strings.))
The methods with "value" in their names are KVC methods.
But, as Randy says, using your app's model is a better way to go, or passing the information directly to a property in the destination view controller in prepareForSegue. Using NSUserDefaults would not be my first, or my second, choice in this situation.
It looks like you're using storyboards already so it should be pretty easy to pass information using prepareForSegue like this.
class DestinationVC : UIViewController {
var destName : String!
override func viewWillAppear(animated: Bool) {
//configure UI with the destName
self.label.text = destName
}
}
class PresentingVC : UIViewController {
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let destinationVC = segue.destinationViewController as? DestinationVC {
destinationVC.destName = "Some String to Pass"
}
}
}
As already mentioned NSUserDefaults is not ideal. You will also be loosing type safety and relying on string matching with keys in NSUserDefaults rather than autocompleting and compiler checking with a var on the destinationVC. It's also good practice to limit where your data is kept and where it could be altered. Storing something in NSUserDefaults when the use case is quite confined will make it more difficult to write focussed tests and make it vulnerable to change from any class anywhere in the app. It may be an edge case but starting a pattern like this in your app could expose you to all sorts of side effect bugs in the future.
Ultimately, this type of information should be passed from view controller to view controller in a model via a delegate. That would be the "appropriate" way to achieve this behavior via a true MVC pattern.
Having said that; I think the quickest fix for you would be not to use segues and to avoid NSUserDefaults all together.
Try the following...
func chooseFriend(sender: UIButton) {
let requestIndex = sender.tag
let friendChosen = self.friends.objectAtIndex(requestIndex) as! String
// Instantiate the second view controller via it's identifier in the storyboard
if let secondViewController = self.storyboard?.instantiateViewControllerWithIdentifier("SecondViewControllerIdentifier") as? SecondViewController {
// Set the chosen friend
secondViewController.friendChosen = friendChosen
self.presentViewController(secondViewController, animated: true, completion: nil)
}
}
And in the SecondViewController add the following property.
var friendChosen: String!
Please make sure the value is not nil prior to passing it to the destination view controller

segue with Dictionary SWIFT

Another question which just should be simple but I am again stumped
I have created a dictionary from a log in.
It is full of values. I now want to segue to a new View controller and take the data with me
So I set up
typealias JSONDict = [String:AnyObject]
func prepareForSegue(segue: UIStoryboardSegue, sender: JSONDict!) {
if (segue.identifier == "toHome"){
var svc = segue.destinationViewController as! OtherViewController
svc.toPass2 = parsed!
Parsed shows as JSONDict - all seems well
Over on OtherViewcontroller I set up to catch it:
var toPass2:JSONDict = JSONDict()
this builds for me quite happily and segues as expected.
But when I
println("check data: \(toPass2)")
I get [:]
Nothing.
It seems to me I have built it - but nothing is coming. I could pass individual values - but all I rally want to do is bring the dictionary with me
I also tried the same thing with the unparsed data string - and this too got me a zero result.
Any help appreciated
in your OtherViewController try this:
class OtherViewController: UIViewController {
var toPass2: JSONDict? {
didSet {
println(toPass2)
}
}
}

Resources