iOS Textfields to variables: Thread 1 SIGABRT crash - ios

I am trying to input information in a textfield and then store the information that is typed out as a variable, to be used in a string. For some reason it crashes whenever I click on the next textfield after the first, or the third after the second. Here is what I have.
var name: String?
#IBAction func nameField(name: UITextField) {
let name = name.text
}
var contact: String?
#IBAction func contactField(contact: UITextField) {
let contact = contact.text
}
var other: String = "nothing"
#IBAction func otherField(other: UITextField) {
if other == nil {
}else{
let other = other.text!
}
}

Related

Unexpectedly found nil while unwrapping an Optional value from XIB file

Error: Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
Hello everyone, I'm making an app that functions like a "To-Do List" app. However I've made two XIBs for my two custom cells that I want displayed on my TableView.
Keep in mind this is my first time using XIBs and building something that's not been a follow along. I've watched many "To-Do List" projects to get an idea of how this should work so I feel as if I'm on the right path. Anyways, to the code.
My prepare for segue func that is giving the error after putting text into the box :
var card: Card?
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
guard segue.identifier == "saveUnwind" else { return }
let taskText = card!.taskCell.taskText.text
let taskBtn = card!.taskCell.taskBtn.isSelected
let gratefulText = card!.gratefulCell.gratefulText.text
let gratefulNum = card!.gratefulCell.gratefulNum.text
card = Card(taskText: taskText!, taskBtn: taskBtn, gratefulText: gratefulText!, gratefulNum: gratefulNum!)
}
Here is my "Card" struct:
struct Card: Equatable {
let gratefulCell = GratefulCell()
let taskCell = TaskCell()
var id = UUID()
var taskText: String
var taskBtn: Bool
var gratefulText: String
var gratefulNum: String
static let dueDateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .short
return formatter
}()
static func ==(lhs: Card, rhs: Card) -> Bool {
return lhs.id == rhs.id
}
static func loadTasks() -> [Card]? {
return nil
}
static func loadSampleTasks() -> [Card]? {
let task1 = Card(taskText: "Test", taskBtn: false, gratefulText: "Test", gratefulNum: "1")
let task2 = Card(taskText: "Test2", taskBtn: false, gratefulText: "Test2", gratefulNum: "2")
return [task1, task2]
}
}
Also here is the XIB file in which I'm trying to access the .text on:
class TaskCell: UITableViewCell {
#IBOutlet weak var taskBtn: UIButton!
#IBOutlet weak var taskText: UITextField!
override func awakeFromNib() {
super.awakeFromNib()
// updateSaveButtonState()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
#IBAction func textEditingChanged(_ sender: UITextField) {
// updateSaveButtonState()
}
#IBAction func isCompleteButtonTapped(_ sender: UIButton) {
taskBtn.isSelected.toggle()
}
}
If there's any other information I can provide please let me know! Any advice is greatly appreciated.

How to connect multiple action to one button Xcode?

I'm a noob in ios development and i have a simple problem which i still cannot solve. The thing is i making a reverse words app and when the user tap the button at first time it will reverse the sample text but then when user tup second time same button it will clear the text inside sample text and result label. So the main question is how to connect the "clear action" to the same button ?
#IBOutlet var actReverseStyle: UIButton!
#IBOutlet weak var sampletext: UITextField!
var sample: String {return sampletext.text ?? ""
}
#IBOutlet weak var resultscreen: UILabel!
#IBAction func actreverse(_ sender: UIButton!) {
let sampleSentence = sample
func reverseWolrdsInSentance(sentanse:String) -> String{
let allWords = sampleSentence.components(separatedBy: " ")
var newSentence = ""
for word in allWords{
if newSentence != ""{
newSentence += " " }
let reverseWord = String(word.reversed())
newSentence += reverseWord}
return newSentence}
resultscreen.text = reverseWolrdsInSentance(sentanse: sampleSentence)
actReverseStyle.setTitle("Clear", for: .normal)
}
}
This may be more convenient.
#IBAction func actreverse(_ sender: UIButton!) {
sender.isSelected.toggle();
if sender.isSelected {
// do reverse
} else {
// do clear
}
}
Just use a boolean flag which toggles every time the action is fired
private var clearAction = false
#IBAction func actreverse(_ sender: UIButton!) {
if clearAction {
// do clear stuff
clearAction = false
} else {
// do reversing stuff
clearAction = true
}
}

How do I store data (user input from a textfield)

I am using Swift and I want to know how I can store user input as a variable (it’s a double).
I'm still in the learning states so I have been watching youtube tutorials on the situation but nothing helped.
My code:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var userInput: UITextField!
override func viewDidLoad() {
userInput.delegate = self
}
#IBAction func Increment(_ sender: UIButton) {
var inc = 0
userInput = userInput + inc
}
#IBAction func Decrement(_ sender: UIButton) {
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
userInput.resignFirstResponder()
}
}
extension ViewController: UITextFieldDelegate{
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
}
The user will be able to enter a number and increment or decrement the number by a certain input value.
//fetch any number from textfield
func getValueFromTextfield() -> Double?{
guard let text = userInput.text else {
print("Missing user input")
return nil
}
guard let doubleValue = Double(text) else {
print("parse failure")
return nil
}
return doubleValue
}
// Now for increment and decrement we have methods be like, Am assuming certain input value as 1 to be used for increment and decrement replace this if needed
func increment(){
// get value
if let value = getValueFromTextfield(){
userInput.text = “\(value + 1)” // increment by 1
}
}
func decrement(){
// get value and check if it’s greater than zero
if let value = getValueFromTextfield(), value > 0{
userInput.text = “\(value - 1)” // decrement by 1
}
}
#IBAction func Increment(_ sender: UIButton) {
increment()
}
#IBAction func Decrement(_ sender: UIButton) {
decrement()
}
These method will help you achieve increment or decrement operation without need to store value in any variable.
If you still want to save value in variable just simply store value returned from method let valueToStore = getValueFromTextfield()
Your code won't compile correctly but the answer to your question is:
let doubleValue = Double(userInput.text!)! converts your code to double
or better using optional unwrapping:
guard let text = userInput.text else {
print("text of userInput is nil")
return
}
guard let doubleValue = Double(text) else {
print("Impossible to parse \(text) into Double")
return
}
print(doubleValue)
#IBAction func Increment(_ sender: UIButton) {
if let text = userInput.text, var doubleText = Double(text) {
doubleText = doubleText + 1
userInput.text = "\(doubleText)"
}
}
Similarly you can write for Decrement.
#IBAction func Increment(_ sender: UIButton) {
var inc = 0
userInput.text = ( Double(userInput.text) + Double(inc) ).rounded(toPlaces: 1)
}

How to keep label results on secondViewController?

I am currently working on an app and I am stuck on the following: I have my mainVC (ReceiveInputVC), which after I enter an input, it goes to the secondVC (TimeLeftVC) and it updates all of its labels with results from the inputs received from the mainVC. My question is: How can I, after clicking on the arrow to go back to the mainVC or even if I close the app, when I click on the arrow from the mainVC to go to the secondVC have my labels showing the same values as before the user closed the application or returned to the main screen?
import UIKit
extension UIViewController {
func hideKeyboard() {
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
view.addGestureRecognizer(tap)
}
#objc func dismissKeyboard() {
view.endEditing(true)
}
}
class ReceiveInputVC: UIViewController {
#IBOutlet weak var hourglassButton: UIButton!
#IBOutlet weak var whatIsYourAgeField: UITextField!
#IBOutlet weak var ageToDieField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.hideKeyboard()
}
#IBAction func arrowBtnPressed(_ sender: Any) {
// When pressed should show go to TimeLeftVC and show last result from the first time user entered the inputs, if nothing has been typed yet and no data has been saved an alert should pop up asking the user to enter an input on both fields
}
#IBAction func hourglassBtnPressed(_ sender: Any) {
let checkAgeField: Int? = Int(whatIsYourAgeField.text!)
let checkDyingAgeField: Int? = Int(ageToDieField.text!)
if (whatIsYourAgeField.text == "" || ageToDieField.text == "") || (whatIsYourAgeField.text == "" && ageToDieField.text == "") {
alert(message: "You must enter an input on both fields")
} else if checkAgeField! < 1 || checkDyingAgeField! > 100 {
alert(message: "You must enter an age higher than 1 and a dying age lower than 100")
} else if (checkAgeField! > checkDyingAgeField!) || (checkAgeField! == checkDyingAgeField!) {
alert(message: "You must enter an age lower than a dying age")
} else {
performSegue(withIdentifier: "goToSecondScreen", sender: self)
}
}
func alert(message: String, title: String = "Alert") {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Try Again", style: UIAlertAction.Style.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
// Passing the data entered from ReceiveInputVC to TimeLeftVC
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToSecondScreen" {
let destinationTimeLeftVC = segue.destination as! TimeLeftVC
destinationTimeLeftVC.ageReceived = whatIsYourAgeField.text
destinationTimeLeftVC.ageToDieReceived = ageToDieField.text
}
}
}
import CircleProgressBar
class TimeLeftVC: UIViewController {
var ageReceived: String! // receive whatIsYourAgeField data from ReceiveInputVC
var ageToDieReceived: String! // receive ageToDieField data from ReceiveInputVC
#IBOutlet weak var yearsLeftLabel: UILabel!
#IBOutlet weak var daysLeftLabel: UILabel!
#IBOutlet weak var hoursLeftLabel: UILabel!
#IBOutlet weak var progressBar: CircleProgressBar!
override func viewDidLoad() {
super.viewDidLoad()
createResults()
}
func createResults() {
if let userAge = Int(ageReceived), let dyingAge = Int(ageToDieReceived) {
let yearsLeft = dyingAge - userAge
let daysLeft = yearsLeft * 365
let hoursLeft = daysLeft * 24
// Update UI
yearsLeftLabel.text = "\(yearsLeft)"
daysLeftLabel.text = "\(daysLeft)"
hoursLeftLabel.text = "\(hoursLeft)"
let percentage = (CGFloat(yearsLeft) / CGFloat(dyingAge)) * 100
let formatted = String(format: "%.1f", percentage)
// Update Circle Progress Bar
progressBar.setHintTextGenerationBlock { (progress) -> String? in
return String.init(format: "\(formatted)%%", arguments: [progress])
}
progressBar.setProgress(percentage/100, animated: true, duration: 4.0)
}
}
Project on GitHub: https://github.com/mvvieira95/Time-Life.git
You can use Coredata or another data base or user default
User default implementation:
#IBAction func arrowBtnPressed(_ sender: Any) {
UserDefaults.standard.set("your input values from text field or ...", forKey: "key")
}
In second view controller get it with
UserDefaults.standard.string(forKey: "key")
You can save and restore states with these methods
application:shouldSaveApplicationState and application:shouldRestoreApplicationStat.
Example:
func application(_ application: UIApplication,
shouldSaveApplicationState coder: NSCoder) -> Bool {
// Save the current app version to the archive.
coder.encode(11.0, forKey: "MyAppVersion")
// Always save state information.
return true
}
func application(_ application: UIApplication,
shouldRestoreApplicationState coder: NSCoder) -> Bool {
// Restore the state only if the app version matches.
let version = coder.decodeFloat(forKey: "MyAppVersion")
if version == 11.0 {
return true
}
// Do not restore from old data.
return false
}
You can explore the document in https://developer.apple.com/documentation/uikit/view_controllers/preserving_your_app_s_ui_across_launches?language=objc
Thanks guys, I came up with a solution:
class ReceiveInputVC: UIViewController {
#IBAction func arrowBtnPressed(_ sender: Any) {
let defaults = UserDefaults.standard
if let _ = defaults.object(forKey: "yearsSaved"), let _ = defaults.object(forKey: "daysSaved"), let _ = defaults.object(forKey: "hoursSaved") {
performSegue(withIdentifier: "goToSecondScreen", sender: self)
} else {
alert(message: "You must first enter an input")
}
}
class TimeLeftVC: UIViewController {
var ageReceived: String! // receive whatIsYourAgeField data from ReceiveInputVC
var ageToDieReceived: String! // receive ageToDieField data from ReceiveInputVC
#IBOutlet weak var yearsLeftLabel: UILabel!
#IBOutlet weak var daysLeftLabel: UILabel!
#IBOutlet weak var hoursLeftLabel: UILabel!
#IBOutlet weak var progressBar: CircleProgressBar!
override func viewDidLoad() {
super.viewDidLoad()
let defaults = UserDefaults.standard
yearsLeftLabel.text = defaults.object(forKey: "yearsSaved") as? String
daysLeftLabel.text = defaults.object(forKey: "daysSaved") as? String
hoursLeftLabel.text = defaults.object(forKey: "hoursSaved") as? String
}
override func viewWillAppear(_ animated: Bool) {
createResults()
}
func createResults() {
if let userAge = Int(ageReceived), let dyingAge = Int(ageToDieReceived) {
let yearsLeft = dyingAge - userAge
let daysLeft = yearsLeft * 365
let hoursLeft = daysLeft * 24
// Update UI
yearsLeftLabel.text = "\(yearsLeft)"
daysLeftLabel.text = "\(daysLeft)"
hoursLeftLabel.text = "\(hoursLeft)"
// Store Data
let defaults = UserDefaults.standard
defaults.set(yearsLeftLabel.text, forKey: "yearsSaved")
defaults.set(daysLeftLabel.text, forKey: "daysSaved")
defaults.set(hoursLeftLabel.text, forKey: "hoursSaved")
// Update Circle Progress Bar
let percentage = (CGFloat(yearsLeft) / CGFloat(dyingAge)) * 100
let formatted = String(format: "%.1f", percentage)
progressBar.setHintTextGenerationBlock { (progress) -> String? in
return String.init(format: "\(formatted)%%", arguments: [progress])
}
progressBar.setProgress(percentage/100, animated: true, duration: 4.0)
}
}
Having troubles now updating that progressBar when I go back to the view...

outlets renaming themselves? breaking geocodeAddressString()?

Environment:Xcode-8iOS-10Swift-3
Overview:
I've got what, to me, is a bizarre issue with respect to Outlets, which seem to change the name of their target when being setup and, I believe, is the source of the problems I'm having with geocodeAddressString()
A Bit Of Backstory:
My view has a number of elements, but for the purposes of this posting, I'm primarily concerned about the UITextField's and how I believe they are affecting my MKMapView code (based somewhat on comments I saw here)
My UITextField's are utilizing a slightly modified form of an extension (originally by 'nhgrif') which I found here where the aim is to be able to setup a daisy-chain of textfields such that hitting the Next (or Return) button on the pop-up keyboard will automatically proceed to the desired next (or in some cases, previous) textfield.
private var kAssociationKeyNextField: UInt8 = 0
private var kAssociationKeyPreviousField: UInt8 = 1 // I added this
extension UITextField {
#IBOutlet var nextField: UITextField? {
get { return objc_getAssociatedObject(self, &kAssociationKeyNextField) as? UITextField }
set(newField) { objc_setAssociatedObject(self, &kAssociationKeyNextField, newField, .OBJC_ASSOCIATION_RETAIN) }
}
// I added the following
#IBOutlet var previousField: UITextField? {
get { return objc_getAssociatedObject(self, &kAssociationKeyPreviousField) as? UITextField }
set(newField) { objc_setAssociatedObject(self, &kAssociationKeyPreviousField, newField, .OBJC_ASSOCIATION_RETAIN) }
}
}
From the Xcode / Storyboard perspective, this provides the following UI's for setting the next (and/or previous) field in the daisy-chain:
Drilling down
I'm not sure how to really explain the issue I'm seeing other than with a screen-capture video, but since I cannot figure out how to post such here, a bunch of screenshots will have to do...
Start with the Name field, and set the nextField to Address:
Then select the Address field and set the previousField to Name and the nextField to City:
So far, everything seems to be working fine...
Now select the City field and set the previousField to Address and the nextField to State:
Yikes! Note that the name associated with the State field is now "Next Field"
Continue with the State field, setting the previousField to City and nextField to Zipcode:
The State field still shows up as "Next Field" - and now the Zipcode field ALSO shows up as "Next Field"
Finish with the Zipcode field, setting the previousField to State - intentionally leaving the nextField unset:
Some More Code
Here is most of the rest of this particular view class's code
class NewLocationViewController: UIViewController, CLLocationManagerDelegate, UITextFieldDelegate {
#IBOutlet weak var doGeoLocate: UISwitch!
#IBOutlet weak var name: UITextField!
#IBOutlet weak var address: UITextField!
#IBOutlet weak var city: UITextField!
#IBOutlet weak var state: UITextField!
#IBOutlet weak var zipcode: UITextField!
#IBOutlet weak var done: UIBarButtonItem!
#IBOutlet weak var map: MKMapView!
var coords: CLLocationCoordinate2D?
var locationManager: CLLocationManager = CLLocationManager()
var currentLocation: CLLocation!
override func viewDidLoad() {
super.viewDidLoad()
name.delegate = self
address.delegate = self
city.delegate = self
state.delegate = self
zipcode.delegate = self
locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.delegate = self
locationManager.startUpdatingLocation()
}
currentLocation = nil
doGeoLocate.isOn = false
map.isHidden = true
done.isEnabled = false
navigationController?.isNavigationBarHidden = false
navigationController?.isToolbarHidden = false
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if doGeoLocate.isOn == true {
textField.resignFirstResponder()
}
else if textField.nextField == nil {
if (!checkFields()) {
// walk back up chain to find last non-filled text-field...
var tmpField = textField
while ((tmpField.previousField != nil) && (tmpField.previousField?.hasText)!) {
tmpField = tmpField.previousField!
}
tmpField.previousField?.becomeFirstResponder()
}
else {
textField.resignFirstResponder()
}
}
else {
textField.nextField?.becomeFirstResponder()
}
return checkFields()
}
func checkFields() -> Bool {
//... if doGeoLocate switch is on - return true
//... if ALL fields are populated, call geocodeAddress() and return true
//... otherwise return false
}
func geocodeAddress() {
print("GA") //#=#
let geoCoder = CLGeocoder()
let addr = "\(address.text) \(city.text) \(state.text) \(zipcode.text)"
print("ADDR: `\(addr)'")//#=#
geoCoder.geocodeAddressString(addr, completionHandler: {
(placemarks: [CLPlacemark]?, error: NSError?) -> Void in
print("IN geocodeAddressString")//#=#
//if error.localizedDescription.isEmpty == false {
// print("Geocode failed with error: \(error.localizedDescription)")
//}
//else if placemarks!.count > 0 {
let placemark = placemarks![0]
let location = placemark.location
self.coords = location!.coordinate
self.map.isHidden = false
//}
} as! CLGeocodeCompletionHandler) //<<<=== NOTE THIS LINE
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
//...
}
#IBAction func toggleGeoLocate(_ sender: AnyObject) {
//...
}
#IBAction func useNewLocation(_ sender: AnyObject) {
//...
}
}
Upon running the app, filling in all the fields, when I click on the 'Done' button in the number-keypad associated with the Zipcode field - I get an exception. The debugging log looks like this:
TFSR: (TFSR Optional("Name") => Optional("Address"))
Returning false
TFSR: (TFSR Optional("Address") => Optional("City"))
Returning false
TFSR: (TFSR Optional("City") => Optional("State"))
Returning false
TFSR: (TFSR Optional("State") => Optional("Zipcode"))
Returning false
GA
ADDR: `Optional("2112 Murray Avenue ") Optional("Pittsburgh ") Optional("PA") Optional("15217")'
(lldb)
The exception shows up as:
func geocodeAddress() {
//...
geoCoder.geocodeAddressString(addr, completionHandler: {
(placemarks: [CLPlacemark]?, error: NSError?) -> Void in
//...
} as! CLGeocodeCompletionHandler) //<<< Thread 1: EXC_BREAKPOINT (code=1, subcode=0x10006c518)
}
And yes, I verified that I have no breakpoints set in the code
SummationI'm reasonably sure that the geocodeAddressString() code is correct (I used it in another app for Swift-2), but I'm very suspicious of the way the State and Zipcode Outlets get renamed when I attempt to chain them with the other fields.Anyone have any ideas?
I'd suggest getting rid of those various casts:
func geocodeAddress() {
//...
geoCoder.geocodeAddressString(addr) { placemarks, error in
//...
}
}
It's easiest to let it infer the correct types for you.
Regarding the naming of outlets, IB is trying to make life easier for you, naming them for you in the absence of any specified name. But with these additional outlets, its default algorithm is falling short. You can probably get around this by naming the outlets yourself in the "Document" section of the "Identity Inspector".

Resources