I have a screen with several switches for the user to select an item. I want to restrict the user to only selecting one item.
I declare this variable at the top of the class, before the viewDidLoad function:
var instrumentCounter:Int = 0
I declare outlet variables for each switch:
#IBOutlet weak var guitarPlay: UISwitch!
I have a function for each switch as well (note - I have some print statements in for troubleshooting):
#IBAction func guitarSwitch(_ sender: Any) {
if self.guitarPlay.isOn == false
{
print("Top: \(instrumentCounter)")
instrumentCounter -= 1
print("Top: \(instrumentCounter)")
}
else if self.guitarPlay.isOn && instrumentCounter == 1
{
self.guitarPlay.setOn(false, animated: true)
showTooManyAlert()
print("Middle: \(instrumentCounter)")
}
else{
instrumentCounter += 1
print(instrumentCounter)
}
}
Here is the function for the alert:
func showTooManyAlert(){
let alertController = UIAlertController(title: "Alert", message:
"You can only select one instrument. Unselect another instrument to select this one.", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Okay", style: .default))
self.present(alertController, animated: true, completion: nil)
}
And here's what's happening: The function for the switch works up to a certain point. I forgot to say that when the screen loads, if the user has already selected an instrument in their profile, the instrumentCounter variable increments by 1. So even before the user does anything, that variable is set to 1.
When I tap the guitar switch (which is currently off -- another switch is on), the first part of the code is skipped and the middle part executes -- the guitar switch turns off, and the alert displays. But then the first part of the code executes: instrumentCounter -= 1.
I'm at a loss as to why that is happening.
I figured it out. I changed the switches' Sent Event from "Value Changed" to "Touch Up Inside."
Related
My app has a "Create Account" view controller (shown below) that prompts the user to enter a username and password. Whenever I segue to another view controller, I get a pop-up action sheet prompting to save the password in the keychain.
This is a nifty little freebie IF the user successfully creates the new account. But I get this same pop-up if the user hits the cancel (back) button in the navigation bar, if they select the option to use Facebook login instead of creating an account, or any other means for leaving this view controller (see figures below).
How can I get this popup to ONLY show up when the user successfully creates a new account?
EDIT: Per request, here is the code that is related to the segues that result in the appearance of the "Save Password" action sheet.
from CreateAccountViewController.swift:
class CreateAccountViewController : UIViewController
{
// ... bunch of irrelevant code deleted ...
// bound to "Connect with Facebook" button (see image below)
#IBAction func switchToFacebook(_ sender : UIButton)
{
performSegue(.SwitchToFacebookLogin, sender: sender)
}
// ... bunch of irrelevant code deleted ...
}
extension CreateAccountViewController : GameServerAlertObserver
{
// callback based on response from GameCenter after
// submitting a "create new user" request
func handleConnectionResponse(_ response:GameServerResponse )
{
switch response
{
// ... other response cases removed ...
case .UserCreated:
self.removeSpinner()
performSegue(.CreateAccountToStartup, sender: self)
default:
response.displayAlert(over: self, observer: self)
self.removeSpinner()
}
}
// Functions defined in the GameServerAlertObserver protocol
// to handle user response to "User Exists Popup" (figure below)
func ok()
{
// user chose to enter new password... clear the existing username field
usernameTextField.text = ""
}
func cancel()
{
// segue back to the startup view controller
performSegue(.CreateAccountToStartup, sender: self)
}
func goToLogin()
{
// segue to the login view controller
performSegue(.SwitchToAccountLogin, sender:self)
}
}
from UIViewController_Segues:
enum SegueIdentifier : String
{
case LoserBoard = "loserBoard"
case CreateAccount = "createAccount"
case AccountLogin = "accountLogin"
case FacebookLogin = "facebookLogin"
case SwitchToFacebookLogin = "switchToFacebookLogin"
case SwitchToAccountLogin = "switchToAccountLogin"
case CreateAccountToStartup = "createAccountToStartup"
case AccountLoginToStartup = "accountLoginToStartup"
case FacebookLoginToStartup = "facebookLoginToStartup"
case UnwindToStartup = "unwindToStartup"
}
extension UIViewController
{
func performSegue(_ target:SegueIdentifier, sender:Any?)
{
performSegue(withIdentifier: target.rawValue, sender: sender)
}
}
from GameServerAlert.swift:
protocol GameServerAlertObserver
{
func ok()
func cancel()
func goToLogin()
}
extension GameServerResponse
{
func displayAlert(over controller:UIViewController, observer:GameServerAlertObserver? = nil)
{
var title : String
var message : String
var actions : [UIAlertAction]
switch self
{
// ... deleted cases/default which don't lead to segue ...
case .UserAlreadyExists:
title = "User already exists"
message = "\nIf this is you, please use the login page to reconnect.\n\nIf this is not you, you will need to select a different username."
actions = [
UIAlertAction(title: "Go to Login page", style: .default, handler: { _ in observer?.goToLogin() } ),
UIAlertAction(title: "Enter new username", style: .default, handler: { _ in observer?.ok() } ),
UIAlertAction(title: "Cancel", style: .cancel, handler: { _ in observer?.cancel() } )
]
}
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
actions.forEach { (action) in alert.addAction(action) }
controller.present(alert,animated:true)
}
}
Examples from the simulator:
Create Account - (user enters username and password for new account here.)
Facebook Login
If user decides to use Facebook to log in rather than creating a user account, they are taken to this view (which I still haven't fleshed out). Note that the "Save Password" action sheet has popped up.
User Exists Popup
If user attempts to create an account with a username that already exists, they will be presented with this popup. If they select Cancel, they are taken back to the startup screen (see below). If they select Enter new username, they are kept on the same screen with the username cleared out. If they select Login, they are taken to the login screen.
Startup Screen
If the user selects Cancel above, they are brought back here. Again, note that the "Save Password" action sheet has popped up.
What I do to avoid the automatic Password saving action sheet when the user :
dismiss the login view controller ;
pop the view controller ;
use interactive pop gesture.
=>
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
passwordTextField.textContentType = .password
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if isMovingFromParent || isBeingDismissed || parent?.isBeingDismissed == true {
passwordTextField.textContentType = nil
}
}
Sorry about the short answer, I don't usually post on this site. This is the password Autofill that is happening on your device when the create user screen is dismissed.
Apple Documentation: https://developer.apple.com/documentation/security/password_autofill
Here is a link to a site that goes over all the requirements very well: https://developerinsider.co/ios12-password-autofill-automatic-strong-password-and-security-code-autofill/
Add a condition before running the code block which shows the action sheet. You can do this simply with an if statement. This statement must check if the account has been successfully created or not. Code block which shows action sheet must run only when the condition is true.
I'm enrolled in a Swift/iOS Bootcamp course on Udemy. I've not had any issues prior to this one with any of the code or lessons, but this has me stumped. The module is about setting up UIAlertController and UIAlertAction, and resetting a quiz app to the beginning of the quiz. The quiz works as it should (at this point at least), however, once the Alert pops up, I can't click on it. It doesn't recognize any of the taps or clicks or move forward to the function that should be called when the click is received.
https://imgur.com/Z7Oc6kU
I'm following the code as written in the course, but one of the issues could possibly be that the course is using an older version of Swift/Xcode than I am, but I'm not 100% positive. That could be causing some confusion.
However, I have tried to utilize the Apple API Documentation, at one point, copying all of their sample code and adjusting to fit my needs (in terms of function to call after 'OK'), but even that code didn't register a click. I've searched on Stack, altered code based on other forums, and nothing has worked.
func nextQuestion() {
if questionNumber <= allQuestions.list.count - 1 {
var nextQuestion = allQuestions.list[questionNumber]
questionLabel.text = nextQuestion.questionText
}
else {
let alert = UIAlertController(title: "Quiz Over", message: "You've Finished the Quiz, Press Restart to Start Over", preferredStyle: .alert)
let restartAction = UIAlertAction(title: "Restart", style: .default) { (UIAlertAction) in
self.startOver()
}
alert.addAction(restartAction)
self.present(alert, animated: true, completion: nil)
}
}
As mentioned, the 'Restart' button on the Alert should force the quiz to restart. The startOver() function resets the questionNumber to 0, and calls a nextQuestion() function, as well as printing a line for me to know if the function has been called, which would mean that the tap is registered, but the startOver() function is incorrect, but it won't print the line, which indicates that the function isn't being called at all.
Full ViewController:
import UIKit
class ViewController: UIViewController {
//Place your instance variables here
let allQuestions = QuestionBank()
var pickedAnswer : Bool = false
var questionNumber : Int = 0
#IBOutlet weak var questionLabel: UILabel!
#IBOutlet weak var scoreLabel: UILabel!
#IBOutlet var progressBar: UIView!
#IBOutlet weak var progressLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let firstQuestion = allQuestions.list[0]
questionLabel.text = firstQuestion.questionText
}
#IBAction func answerPressed(_ sender: AnyObject) {
if sender.tag == 1 {
pickedAnswer = true
}
else if sender.tag == 2 {
pickedAnswer = false
}
checkAnswer()
nextQuestion()
}
func updateUI() {
}
func nextQuestion() {
questionNumber += 1
if questionNumber <= allQuestions.list.count - 1 {
var nextQuestion = allQuestions.list[questionNumber]
questionLabel.text = nextQuestion.questionText
}
else {
let alert = UIAlertController(title: "Quiz Over", message: "You've Finished the Quiz, Press Restart to Start Over", preferredStyle: .alert)
let restartAction = UIAlertAction(title: "Restart", style: .default) { action in self.startOver()
}
alert.addAction(restartAction)
self.present(alert, animated: false, completion: nil)
}
}
func checkAnswer() {
if pickedAnswer == allQuestions.list[questionNumber].answer{
print("good")
}
else if pickedAnswer != allQuestions.list[questionNumber].answer{
print("bad")
}
}
func startOver() {
questionNumber = -1
nextQuestion()
print(questionNumber)
print("Restarting")
}
}
I can't reproduce any issue based on the code you've shown, as you can see:
... so I think this is may be a problem with your Xcode setup. Possible issue:
You are building to the iPhone XR simulator which is slow.
You may have an old computer.
And you seem to have turned on Slow Animations in the Simulator app by mistake.
At the least try turning off Slow Animations and build for the 5s simulator. This will make the Simulator a lot more responsive. But on old machinery there may still be issues.
Try to present in main thread:
DispatchQueue.main.async {
self.present(alert, animated: true, completion: nil)
}
I have two UIViewController. In the first one, I have a button that adds some views, one at a time, to the main view. In the second one, I set up a store, so that when I press on a button I unlock some features of my app. Now, I perfectly know (I hope) how to handle the part where I make the VCs comunicate and I trigger some other easy functions, what I don't know is how to make the store button increase the button's functions.
WHAT I NEED:
Right now the button adds a maximum of 10 views (complete version). I want that before the user buys my app, he gets to add a maximum of 3 views and then, when he buys it, the function I already have (the one to add 10 views)starts to work and replaces the other one.
MAIN VIEW CONTROLLER
var messageArray = [UIView] ()
I attached all of my UIView from the storyboard and I appended them to my array in the viewDid load like this: messageArray.append(View1)
#IBAction func addMessageViewButton(_ sender: Any) {
let hiddenViews = messageArray.filter { $0.isHidden }
guard !hiddenViews.isEmpty else {
let sheet = UIAlertController(title: "max reached", message: nil, preferredStyle: .actionSheet)
let ok = UIAlertAction(title: "OK", style: .cancel, handler: nil)
let closeAll = UIAlertAction(title: "Close all", style: .destructive) { (addMessage) in
view1.isHidden = true
view2.isHidden = true
view3.isHidden = true
view4.isHidden = true
view5.isHidden = true
view6.isHidden = true
view7.isHidden = true
view8.isHidden = true
view9.isHidden = true
view10.isHidden = true
}
sheet.addAction(ok)
sheet.addAction(closeAll)
present(sheet, animated: true, completion: nil)
return
}
let randomHiddenView = hiddenViews.randomElement()
randomHiddenView?.isHidden = false
}
SHOP VIEW CONTROLLER
Here I won't post all of the code because it would be too much and of course unnecessary, since the important thing to know here is that there's a button and if the user presses it and he proceeds with the purchase, he will get the function I posted up here working instead of the one that allows him to have just 3 views.
func unlock() {
let appdelegate = UIApplication.shared.delegate
as! AppDelegate
appdelegate.viewController!.functionToHave10Views()
//viewControlled is declared in the app delegate like this ` var viewController: ViewController?`
//I know I don't physically have `functionToHave10Views()`, but I guess I'll turn the code of my button into a function, so just to be clear, I'm referring to that function.
buyButton.isEnabled = false
}
In your main view controller:
var isLocked = true
#IBAction func addMessageViewButton(_ sender: Any) {
if isLocked {
// Do something for when is locked
} else {
// Do something for when is unlocked
}
}
Then in your shop view controller:
func unlock() {
let appdelegate = UIApplication.shared.delegate as! AppDelegate
appdelegate.viewController!.isLocked = false
buyButton.isEnabled = false
}
I'm trying to make a password protected view controller.
so far -
Created storyboard -
on viewcontroller - created hard coded log in -
prints to console if successful or not.
textfields etc...
#IBOutlet weak var untext: UITextField!
#IBOutlet weak var pwtext: UITextField!
let username = "admin"
let password = "adminpw"
override func viewDidLoad() {
super.viewDidLoad()
pwtext.isSecureTextEntry = true
}
#IBAction func loginbtn(_ sender: Any) {
if untext.text == username && pwtext.text == password
{
print("log in succesful")
} else {
print("log in failed")
}
}
The issue I have, once I press the login button, it takes me to the admin page if successful or not.
How can I print a notification - on screen - if unsuccessful and remain on the current view controller, and if successful, take me to admin view controller?
You can either use a segue or instantiateViewController. But in this example I'll use instantiateViewController (Images). (But commented how to use a segue)
Add a class and an identifier to your secondary ViewController
Choose between my Segue or Instantiate. (Check my comments in the code)
If login is succeeded, either perform the segue or navigate using instantiate.
Happy coding. :D
But first off, let's take a look at the code you provided.
#IBAction func loginbtn(_ sender: Any)
{
if untext.text == username && pwtext.text == password
{
print("login succeeded")
//1. using instantiateViewController
if let storyboard = storyboard
{
//Check my image below how to set Identifier etc.
// withIdentifier = Storyboard ID & "ViewController" = Class
let vc = storyboard.instantiateViewController(withIdentifier: "ViewController") as! ViewController
self.present(vc, animated: false, completion: nil)
}
//2. Use segue (I'll wrap this with a comment incase you copy)
//self.performSegue(withIdentifier: "SegueID", sender: self)
}
else
{
//Setting up an "AlertController"
let alert = UIAlertController(title: "Login failed", message: "Wrong username / password", preferredStyle: UIAlertController.Style.alert)
//Adding a button to close the alert with title "Try again"
alert.addAction(UIAlertAction(title: "Try again", style: UIAlertAction.Style.default, handler: nil))
//Presentating the Alert
self.present(alert, animated: true, completion: nil)
}
}
Click on the yellow dot on your ViewController (On the ViewController where you want the login-page to take you)
Click on the icon like I've. (Which is blue) and set a Class + Storyboard ID.
NOTE! IF you wanna use a segue, make SURE you have a connection between ViewController(Login) and ViewController1
Assuming you use segues for navigation, you can put a "general purpose" segue (drag from your controller, instead of any controls in it) and assign it an ID (Identifier in attribute inspector of the segue in Storyboard). After that you can conditionally invoke segue from the parent controller class with your code:
if passwordCorrect {
performSegue(withIdentifier: "SegueID", sender: nil)
}
I'm attempting to make an alert the input view for a textfield, since i have max. 3 choices to make and my customer wants to have it look this way.
Basically no problem, i can tap on the textfield, resulting in showing the alert. After choosing the value in the textfield gets updated correctly, but then, the cursor stays in the textfield (im assuming because of the tap, the textfield is first responder)
The problem now is, when i tap on that very same textfield again while the cursor is in it, the default keyboard opens. So what can i do to stop it from doing this?
I have tried the following things on various locations in code:
resignFirstResponder()
becomeFirstResponder()
Also, when im currently editing another textfield with the keyboard, im able to show the alert when tapping the corresponding textfield. this results in the keyboard covering the alert, since im also not able to close the keyboard when this occurs.
Attempt against this: (Written in Event methods of other textfields
if (alertView != nil) {
alertView?.dismiss()
} //Not very professional, but still in development :)
Here are some important code snippets to understand how i built it up:
This function is the EditingDidBegin Event of the textfield
#IBAction func TextboxUnitEditing(sender: UITextField) {
//One try of preventing the cursor (not working)
tableView.endEditing(true)
...
/* Unrelated Code here */
...
alertView = SwiftAlertView(...)
...
/* Unrelated Code here */
...
alertView.show()
}
Delegate method of the alert
func alertView(alertView: SwiftAlertView, clickedButtonAtIndex buttonIndex: Int) {
//buttonIndex == 1 means, cancel was tapped
if (buttonIndex == -1) {
currentCell = nil
tableView.endEditing(true)
return
}
...
/* Change value in textfield */
}
So my question is, what goes wrong? What do i need to call to get rid of the cursor? If there is anything more you need to know, please tell me and i can provide more information.
Thanks in advance.
EDIT:
Solved. Instead of:
self.tableView.endEditing(true)
i had to do this:
dispatch_async(dispatch_get_main_queue(),{
self.tableView.endEditing(true)
})
You can use this extension to do the job you seem to be describing, no text boxes needed? You call it with the line.
showInfo(message: error!.localizedDescription)
Here is the extension behind it.
protocol ShowsInfo {}
extension ShowsInfo where Self: UIViewController {
func showInfo(title: String = "Info", message: String) {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .Alert)
alertController.addAction(UIAlertAction(title: "Ok", style: .Default, handler: nil))
dispatch_async(dispatch_get_main_queue()) {
self.presentViewController(alertController, animated: true, completion: nil)
}
}
}
Instead of
self.tableView.endEditing(true)
It has to be done on the main thread
dispatch_async(dispatch_get_main_queue(),{
self.tableView.endEditing(true)
})
Otherwise this line will be skipped