How to connect multiple action to one button Xcode? - ios

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
}
}

Related

Swift - Accessing implicitly unwrapped variable gives a nil error

I'm following a tutorial on CoreData and I've been following it exactly, yet when they run the app, everything works and saves correctly, yet I get a nil error. The tutorial is a few years old, so I'm not sure if something has been udpated in the way CoreData works. It's an app to save goals.
Here's the first view controller where you enter the text of the goal and if it is short or long term:
import UIKit
class CreateGoalViewController: UIViewController, UITextViewDelegate {
#IBOutlet weak var goalTextView: UITextView!
#IBOutlet weak var shortTermButton: UIButton!
#IBOutlet weak var longTermButton: UIButton!
#IBOutlet weak var nextButton: UIButton!
var userGoalType: GoalType = .shortTerm
override func viewDidLoad() {
super.viewDidLoad()
nextButton.bindToKeyboard()
shortTermButton.setSelectedColor()
longTermButton.setDeselectedColor()
print("\(userGoalType)")
goalTextView.delegate = self
}
#IBAction func nextButtonPressed(_ sender: Any) {
if goalTextView.text != "" && goalTextView.text != "What is your goal?" {
guard let finishVC = storyboard?.instantiateViewController(withIdentifier: "FinishVC") as? FinishGoalViewController else {return}
finishVC.initData(description: goalTextView.text!, type: userGoalType)
print("\(finishVC.goalType.rawValue) after next button pressed")
performSegue(withIdentifier: "goToFinish", sender: self)
}
}
#IBAction func longTermButtonPressed(_ sender: Any) {
userGoalType = .longTerm
longTermButton.setSelectedColor()
shortTermButton.setDeselectedColor()
print("\(userGoalType)")
}
#IBAction func shortTermButtonPressed(_ sender: Any) {
userGoalType = .shortTerm
shortTermButton.setSelectedColor()
longTermButton.setDeselectedColor()
print("\(userGoalType)")
}
#IBAction func backButtonPressed(_ sender: Any) {
dismiss(animated: true)
}
func textViewDidBeginEditing(_ textView: UITextView) {
goalTextView.text = ""
goalTextView.textColor = UIColor(ciColor: .black)
}
}
And here's the following view controller where you set the number of times you want to do that goal where the CoreData functions are:
import UIKit
import CoreData
class FinishGoalViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var createButton: UIButton!
#IBOutlet weak var pointsTextField: UITextField!
var goalDescription: String!
var goalType: GoalType!
func initData(description: String, type: GoalType) {
self.goalDescription = description
self.goalType = type
}
override func viewDidLoad() {
super.viewDidLoad()
createButton.bindToKeyboard()
pointsTextField.delegate = self
}
#IBAction func createGoalPressed(_ sender: Any) {
if pointsTextField.text != ""{
self.save { finished in
if finished {
dismiss(animated: true)
}
}
}
}
#IBAction func backButtonPressed(_ sender: Any) {
dismiss(animated: true)
}
func save(completion: (_ finished: Bool) -> ()) {
guard let managedContext = appDelegate?.persistentContainer.viewContext else {return}
let goal = Goal(context: managedContext)
goal.goalDescription = goalDescription
goal.goalType = goalType.rawValue
goal.goalCompletionValue = Int32(pointsTextField.text!)!
goal.goalProgress = Int32(0)
do{
try managedContext.save()
print("successfully saved data")
completion(true)
}catch{
debugPrint("Could not save: \(error.localizedDescription)")
completion(false)
}
}
}
I'm getting a nil error in the save function with the goalType.rawValue turning up nil. The goal type is set up in an enum file:
import Foundation
enum GoalType: String {
case longTerm = "Long Term"
case shortTerm = "Short Term"
}
I'm not sure why there's an error. Because in the CreateGoalViewController, I print the goalType.rawValue from the following view controller and it comes up with the correct string, either short or long-term. But when FinishGoalViewController loads, it is all of a sudden nil.
You are initiating and configuring your FinishGoalViewController in nextButtonPressed but you never use it. performSegue(withIdentifier: "goToFinish", sender: self) will create and push a new instance of FinishGoalViewController.
The most simple aproach would be to push your allready configured controller from your curent Controller. Remove performSegue(... and use.
self.navigationController?.pushViewController(finishVC, animated: true)
If you still want to use the segue, remove everything from the nextButtonPressed function, leaving just the performSegue(... line. After that add this function to your CreateGoalViewController controller.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToFinish" {
if let finishVC = segue.destination as? FinishGoalViewController {
// configure finshVC here
}
}
}

Disable button from going to next viewcontroller if textfields aren't filled

I'm having the issue with the textfields being empty and once I hit the button it continues to the next viewcontroller. I want to disable the button until all textfields are complete.
#IBAction func buttontapped(_ sender: Any, forEvent event: UIEvent) {
let loginFunc = Login()
loginFunc.login(First_Nm: First_Nm.text!, Pw: Pw.text!, Last_Name: Last_Name.text!, Email: Email.text!) { jsonString in
let response = jsonString
print(response)
if response.range(of: "failure") == nil {
DispatchQueue.main.async {
self.performSegue(withIdentifier: "goToHomeVC", sender: nil)
}
}
}
Add a check for the UITextFields to see if the text's isEmpty property is true before you proceed to performSegue.
#IBAction func buttontapped(_ sender: Any, forEvent event: UIEvent) {
if First_Nm.text!.isEmpty || Last_Name.text!.isEmpty || Email.text!.isEmpty {
print("Incomplete, show an alert for user's attention!")
return
}
//...
}
Add-on: Also, follow a standard naming convention for your properties. Eg: Instead of First_Nm use firstNameTextField.
If you want to achieve the disable and enable on the button based on textField input you can implement UITextFieldDelegate - textFieldDidChange
The code would look like this:
#IBOutlet weak var loginButton: UIButton!
#IBOutlet weak var firstNameTextField: UITextField!
#IBOutlet weak var lastNameTextField: UITextField!
#IBOutlet weak var passwordTextField: UITextField!
#IBOutlet weak var emailTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
firstNameTextField.delegate = self
lastNameTextField.delegate = self
passwordTextField.delegate = self
emailTextField.delegate = self
}
And on the bottom of the ViewController file you can create extension for UITextFieldDelegate
extension YourViewController: UITextFieldDelegate {
func textFieldDidChangeSelection(_ textField: UITextField) {
checkButtonState()
}
func checkButtonState() {
var validFirstName = false
var validLastName = false
var validPassword = false
var validEmail = false
if firstNameTextField.text != "" {
validFirstName = true
}
if lastNameTextField.text != "" {
validLastName = true
}
if passwordTextField.text != "" {
validPassword = true
}
if emailTextField.text != "" {
validEmail = true
}
if validFirstName && validLastName && validPassword && validEmail {
loginButton.isEnabled = true
} else {
loginButton.isEnabled = false
}
}
}
Then I would also recommend another check when the button is tapped similar to above answer however I think it could be a more extensive check on email and password. (Login might not be as important but during sign up you might want to validate password and email further than not empty)
You can make up your own rules but usually its something along the lines of
Validate password:
• Required 6-8 characters
if string.count < 6 {
print("Password must be more than 6 characters")
}
• Maybe require one capital letter or one number
Validate email:
• Should contain "#"
if !string.contains("#") {
print("probably not a valid email")
}
You can check the strings in the textfield to see if they meet the criteria if not return a login or sign up error to the user. Hopefully this gets you going in the right direction

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...

Hide a website button in my tableViewCell

This is code for my custom tableViewCell
#IBOutlet weak var webButton: UIButton!
func update(place:EClass) {
self.place = place
myLabel.text = place.name
myImage.image = nil
myLabel2.text = place.getDescription()
// IF (didClickWebsite) THE SITE DOESN'T EXIST, MAKE INVISIBLE THE BUTTON webButton
if let url = place.photos?.first?.getPhotoURL(maxWidth: maxWidht) {
myImage.af_setImage(withURL: url)
}
}
#IBAction func goToWebSite(_ sender: Any) {
if let place = place, let delegate = delegate {
delegate.didClickWebsite(place: place)
}
}
where I recently added this button #IBOutlet weak var webButton: UIButton!
and this extension is in the VC of my tableView
extension CourseClass2: PlaceCellDelegate {
func didClickWebsite(place: EClass) {
NearbyZone.getZoneDetails(place: place) { (place) in
if let website = place.details?["website"] as? String, let url = URL(string: website) {
let svc = SFSafariViewController.init(url: url)
self.navigationController?.pushViewController(svc, animated: true)
}
}
}
}
what I would like to do is to tell the func update in my custom tableViewCell that if the function didClickWebsite does not produce any results (in this case when the site does not exist) it has to hide the website button, how caI i do this?

UIButton inside UISegmentedControl action

I am continuing my calculation program to improve my skills in Swift code. Right now I have a problem: I want that an UISegmentedControl select the operator of my operations (+, - ecc..) and when one is selected a UIButton func calculate the values based on the decision of the UISegmentedControl.
Here is the code but is not working.
Thanks,
Matteo
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var firstNumber: UITextField!
#IBOutlet weak var secondNumber: UITextField!
#IBOutlet weak var resultButton: UIButton!
#IBOutlet weak var resultNumber: UILabel!
#IBOutlet weak var operatorSelector: UISegmentedControl!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func operatorSelectorChanged(_ sender: UISegmentedControl) {
switch operatorSelector.selectedSegmentIndex {
case 0:
#IBAction func resultFunction(_ sender: AnyObject) {
if let firstNumberConv :Int = Int(firstNumber.text!), let secondNumberConv :Int = Int(secondNumber.text!) {
let result = firstNumberConv + secondNumberConv
resultNumber.text = "\(result)"
} else { resultNumber.text = "Inserire solo valori numerici"}
}
case 1:
#IBAction func resultFunction(_ sender: AnyObject) {
if let firstNumberConv :Int = Int(firstNumber.text!), let secondNumberConv :Int = Int(secondNumber.text!) {
let result = firstNumberConv - secondNumberConv
resultNumber.text = "\(result)"
} else { resultNumber.text = "Inserire solo valori numerici"}
}
default:
break;
}
}
}
Try removing the lines with #IBAction func ... inside your switch cases.
I think your #IBAction func operatorSelectorChanged should look something like this:
#IBAction func operatorSelectorChanged(_ sender: UISegmentedControl) {
switch operatorSelector.selectedSegmentIndex {
case 0:
if let firstNumberConv = Int(firstNumber.text!), let secondNumberConv = Int(secondNumber.text!) {
let result = firstNumberConv + secondNumberConv
resultNumber.text = "\(result)"
} else { resultNumber.text = "Inserire solo valori numerici"}
case 1:
if let firstNumberConv = Int(firstNumber.text!), let secondNumberConv = Int(secondNumber.text!) {
let result = firstNumberConv - secondNumberConv
resultNumber.text = "\(result)"
} else { resultNumber.text = "Inserire solo valori numerici"}
default:
break;
}
}
A possible solution for your problem is to define an IBAction that calculates the result of adding/subtracting two numbers
#IBAction func calculate(_ sender: AnyObject) {
if let a = Int(firstNumber.text!), let b = IntsecondNumber.text!) {
switch operatorSelector.selectedSegmentIndex {
case 0:
resultNumber.text = "\(a+b)"
case 1:
resultNumber.text = "\(a-b)"
default:
print("❗️unknown operator selected")
}
}
}
You could then bind this action to the sent events from your user interface: for example the Editing Changed event of your text fields (firstNumber and secondNumber) and the Value Changed of your segmented control (operatorSelector)
I created an example project that illustrates this: https://github.com/dev1an/calculator
How to bind multiple events to one IBAction
Write down the IBAction in code (like the calculate action I wrote above). Go to your storyboard and open the assistent editor (AltCmdEnter). Select a UI element (like a text field) in the interface builder. Open the Connections Inspector (AltCmd6) and drag from the circle next to a sent event (for example Editing Changed) to your IBAction code.

Resources