My code was working perfectly with Swift2 and now for some label the variable is displayed but with Optional(....) just before...
Good code on Swift 2
var sstitre1:String!
print(sstitre1)
There is a perform segue which populate a value to sstitre1.
var sstitre1:String?
sstitre1 = json[21].string
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "hugo"
{
if let destinationVC = segue.destination as? MonumentViewController{
destinationVC.sstitre1 = "\(sstitre1)"
}
}
}
With Swift3, i ve get :
Optional(.....)
I want to get rid of Optional.
So i have as recommend on several post from stackoverflow, made some code change.
var sstitre1:String!
If let espoir = sstitre1 {
print (espoir)
}
But unfortunately it still displays Optional....
Pretty weird ....
In the line
destinationVC.sstitre1 = "\(sstitre1)"
always a literal string "Optional(foo)" is assigned to destinationVC.sstitre1 assuming the variable contains "foo"
The solution is to remove the String Interpolation
destinationVC.sstitre1 = sstitre1
PS: You should really use more descriptive variable names
Related
This is a similar approach to Save dictionary to UserDefaults, however, it is intended for SwiftUI, not using a single line like set, so I want to store the value somewhere with a variable so I can call it easily. Also it's different because I'm asking for an initialization.
I have the following:
#Published var mealAndStatus: Dictionary
init() {
mealAndStatus = ["Breakfast": "initial", "Snack": "notSet", "Lunch": "notSet", "Snack2": "notSet", "Dinner": "notSet"]
if let storedDay = UserDefaults.standard.value(forKey: "mealAndStatus") {
mealAndStatus = storedDay as! Dictionary
}
}
1- How do I correctly store that dictionary in UserDefaults in SwiftUI?
2- That init, do I have to call it at the beginning of ContentView? Or can I leave it on the other swift file like that? Not sure how the init gets called.
I already made one with bool working:
#Published var startDay: Bool
init() {
startDay = true
if let storedDay = UserDefaults.standard.value(forKey: "startDay") {
startDay = storedDay as! Bool
}
}
but the dictionary doesn't seem to work. I need to initialize that dictionary and also store it in UserDefaults so I can access it later. Any help is appreciated.
This is the perfect solution I found for SwiftUI:
Store this somewhere, in my case I created a class just for UserDefaults:
#Published var mealAndStatus: [String: Date] =
UserDefaults.standard.dictionary(forKey: "mealAndStatus") as? [String: Date] ?? [:] {
didSet {
UserDefaults.standard.set(self.mealAndStatus, forKey: "mealAndStatus")
}
}
That above initializes the dictionary and also creates a variable to be easily called and use to update the value. This can be modified at lunch time and add new values, that way is initialized with whatever I want.
Furthermore, now on Apple Dev wwdc20 they announced a new way of handling UserDefaults with SwiftUI which may be even better than the above. The propery wrapper is called: #AppStorage.
Using JSONEncoder and JSONDecoder would help you convert to data any struct or dictionary that conforms to codable.
let arrayKey = "arrayKey"
func store(dictionary: [String: String], key: String) {
var data: Data?
let encoder = JSONEncoder()
do {
data = try encoder.encode(dictionary)
} catch {
print("failed to get data")
}
UserDefaults.standard.set(data, forKey: key)
}
func fetchDictionay(key: String) -> [String: String]? {
let decoder = JSONDecoder()
do {
if let storedData = UserDefaults.standard.data(forKey: key) {
let newArray = try decoder.decode([String: String].self, from: storedData)
print("new array: \(newArray)")
return newArray
}
} catch {
print("couldn't decode array: \(error)")
}
return nil
}
// You would put this where you want to save the dictionary
let mealAndStatus = ["Breakfast": "initial", "Snack": "notSet", "Lunch": "notSet", "Snack2": "notSet", "Dinner": "notSet"]
store(dictionary: mealAndStatus, key: arrayKey)
// You would put this where you want to access the dictionary
let savedDictionary = fetchDictionay(key: arrayKey)
On a side note, you probably shouldn't be using standard defaults for storing stuff like this. Storing it as a database, or saving it in a file especially with encryption on eith the database or the file might be a bit safer.
Multiline text input is currently not natively supported in SwiftUI (hopefully this feature is added soon!) so I've been trying to use the combine framework to implement a UITextView from UIKit which does support multiline input, however i've been having mixed results.
This is the code i've created to make the Text view:
struct MultilineTextView: UIViewRepresentable {
#Binding var text: String
func makeUIView(context: Context) -> UITextView {
let view = UITextView()
view.isScrollEnabled = true
view.isEditable = true
view.isUserInteractionEnabled = true
view.backgroundColor = UIColor.white
view.textColor = UIColor.black
view.font = UIFont.systemFont(ofSize: 17)
view.delegate = context.coordinator
return view
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.text = text
}
func frame(numLines: CGFloat) -> some View {
let height = UIFont.systemFont(ofSize: 17).lineHeight * numLines
return self.frame(height: height)
}
func makeCoordinator() -> MultilineTextView.Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UITextViewDelegate {
var parent: MultilineTextView
init(_ parent: MultilineTextView) {
self.parent = parent
}
func textViewDidChange(_ textView: UITextView) {
parent.text = textView.text
}
}
}
I've then implemented it in a swiftUI view like:
MultilineTextView(text: title ? $currentItem.titleEnglish : $currentItem.pairArray[currentPair].english)//.frame(numLines: 4)
And bound it to a state variable:
#State var currentItem:Item
It sort of works. However, the state var currentItem:Item contains an array of strings which I'm then cycling through using buttons which update the string array based on what has been inputted into MultilineTextView. This is where i'm encountering a problem where the MultilineTextView seems to bind to only the first string item in the array, and then it won't change. When I use swiftUI's native TextField view this functionality works fine and I can cycle through the string array and update it by inputting text into the TextField.
I think I must be missing something in the MultilineTextView struct to allow this functionality. Any pointers are gratefully received.
Update: Added model structs
struct Item: Identifiable, Codable {
let id = UUID()
var completed = false
var pairArray:[TextPair]
}
struct TextPair: Identifiable, Codable {
let id = UUID()
var textOne:String
var textTwo:String
}
Edit:
So I've done some more digging and I've found what I think is the problem. When the textViewDidChange of the UITextView is triggered, it does send the updated text which I can see in the console. The strange thing is that the updateUIView function then also gets triggered and it updates the UITextView's text with what was in the binding var before the update was sent via textViewDidChange. The result is that the UITextview just refuses to change when you type into it. The strange thing is that it works for the first String in the array, but when the item is changed it won't work anymore.
It appears that SwiftUI creates two variants of UIViewRepresentable, for each binding, but does not switch them when state, ie title is switched... probably due to defect, worth submitting to Apple.
I've found worked workaround (tested with Xcode 11.2 / iOS 13.2), use instead explicitly different views as below
if title {
MultilineTextView(text: $currentItem.titleEnglish)
} else {
MultilineTextView(text: $currentItem.pairArray[currentPair].textOne)
}
So I figured out the problem in the end, the reason why it wasn't updating was because I was passing in a string which was located with TWO state variables. You can see that in the following line, currentItem is one state variable, but currentPair is another state variable that provides an index number to locate a string. The latter was not being updated because it wasn't also being passed into the multiline text view via a binding.
MultilineTextView(text: title ? $currentItem.titleEnglish : $currentItem.pairArray[currentPair].english)
I thought initially that passing in one would be fine and the parent view would handle the other one but this turns out not to be the case. I solved my problem by making two binding variables so I could locate the string that I wanted in a dynamic way. Sounds stupid now but I couldn't see it at the time.
Anyone can help me?
This is my code:
fileprivate func datapicker(sender : UITextField){
let datePickerView:UIDatePicker = UIDatePicker()
datePickerView.datePickerMode = UIDatePickerMode.date
sender.inputView = datePickerView
datePickerView.addTarget(self, action: #selector(datePickerValueChanged(sender: datePickerView, myText: sender)), for: UIControlEvents.valueChanged)
}
#objc func datePickerValueChanged(sender:UIDatePicker , myText : UITextField) {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = DateFormatter.Style.medium
dateFormatter.timeStyle = DateFormatter.Style.none
myText.text = dateFormatter.string(from: sender.date)
}
#IBAction func fromdateAction(_ sender: UITextField) {
datapicker(sender: sender)
}
Selectors can't be called like that. You don't get to pass the arguments for a selector, the API does.
You have to pass in:
#selector(datePickerValueChanged)
This means that your datePickerValueChanged method can't take 2 arguments, because the API expects only one.
This means that you need a way to know which text field's date picker changed. One simple way to do this is to create a property called focusedTextField:
var focusedTextField: UITextField!
Set this to sender in your dataPicker method:
focusedTextField = sender
And set it to nil when the user ends editing
// in another method that is called when the text field ends editing
focusedTextField = nil
Now, you can remove the second argument from datePickerValueChanged and use focusedTextField instead.
In FirstViewController i'm fetching the response from JSON and want to pass that fetched response to another view controller.Below is the code which i have used so far for parsing and passing the response.
FirstViewController
var fn:String! //globally declared variable
code i have tried for parsing in FirstViewController
do {
let detailsDictionary:NSDictionary = try JSONSerialization.jsonObject(with: data!, options:.allowFragments) as! Dictionary<String, AnyObject> as NSDictionary
print(detailsDictionary)
let details = detailsDictionary["Data"] as! [[String:AnyObject]]
print(details)
for dtl in details
{
self.fn = dtl["Father_Name"] as? String ?? "NA"
print(self.fn) //here i'm getting the exact value from JSON
}
}
}
SecondViewController
In SecondViewController there is a Label called profile_name and want to set that parsed string(fn) as Label's text. for that i declared another variable as global.
var pname:String!
below is the code i have used to fetch the value from FirstViewController.
viewDidLoad()
{
let othervc = FirstViewController()
self.pname = othervc.fn
self.profile_name.text = self.pname
}
Problem : I tried my best efforts to get the desired output but i'm getting nil response.
Please Help.
In Second ViewController
let strName:String!
In First ViewController
let strOne = "This is for testing"
let objstory = self.storyboard?.instantiateViewController(withIdentifier: "yout Secoond ViewController Storybord ID") as! YourSecondViewControllerName
objstory.strNam = strOne
self.navigationController?.pushViewController(objstory, animated: true)
Your updated code just won't work.
let othervc = FirstViewController()
creates a new instance of FirstViewController (not the one that got the JSON).
You should be handling it something like this:
In FirstViewController
let fn = dtl["Father_Name"] as? String ?? "NA"
let svc = SecondViewController() // Or maybe instantiate from Storyboard, or maybe you already have a reference to it
svc.pname = fn
present(svc, animated: true, completion: nil)
Then in SecondViewController
override func viewDidLoad() {
super.viewDidLoad()
profile_name.text = pname
}
I'd suggest you take some time out and re-read Apple's View Controller programming guide.
Original Answer
The problem you have here…
vcvalue.profile_name.text = fn
is that profile_name is nil as the view for the view controller hasn't been loaded at this point.
You should handle this by creating a property in LeftSideMenuViewController
var name: String?
Then set
vcvalue.name = fn
And then in LeftSideMenuViewController
override func viewDidLoad() {
super.viewDidLoad()
profile_name.text = name
}
Also, some basic tips…
Don't force unwrap (!) apart from IBOutlets. You may have to write a bit more code, but you will reduce crashes.
Make #IBOutlets private - this will prevent you accidentally assigning to them as you are now
If you're overriding any viewWill/DidDis/Appear methods, you must call super at some point.
You need to re-read the section on switch/case
So this…
let a = indexPath.row
switch(a)
{
case 0 :
if(a == 0)
{
return 45
}
break
etc
could just be…
switch indexPath.row {
case 0...4:
return 45
case 5:
return 50
default:
break
}
currently my output is displaying selected values from pickerview inside textfields.
now my question is i want to access all these values on submit button and want to display in another view controller how to do this?. let me explain my scenario my first vc is set as collectionview from one of the collectionviewcell m redirecting to this page.
Note: i already know that how to pass data between two view controller. but its not working in my case.Please Help.
Code
#IBAction func StaffAtten_Action(_ sender: Any) {
// let secondVC = self.storyboard?.instantiateViewController(withIdentifier: "StaffAttendence_SecondPage") as! StaffAttendence_SecondPage
//
// secondVC.a = active_text.text!
// secondVC.b = active_text.text!
// secondVC.c = active_text.text!
// secondVC.savedata.append(year.text!)
// secondVC.savedata.append(month.text!)
// secondVC.savedata.append(institute.text!)
}
}
The problem that you are having is that you are instantiating a brand new VC and passing data to it. The VC that is actually presented is not the one you created.
Since you have a segue connecting the two VCs, override prepare(for:sender:)
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? StaffAttendence_SecondPage {
vc.a = ...
vc.b = ...
// pass the rest of the data here...
}
}