I have a scroll view that adds UI elements programmatically, but a button that I've added seems to have a delay in response once tapped both in a simulator and on an iPhone 7 Plus I use to test on it. I'm not sure as to why. I also am getting warning messages each time a text field is edited as well. How do I resolve these issues? Below our the warning messages and some of my code.
Thank you for helping!
Button warning in log:
2017-04-20 14:49:35.087232-0500 HAEForMe[889:369946] Warning: Attempt to present <UIAlertController: 0x102642b00> on <AppName.UserSetupViewController: 0x10303dc00> which is already presenting <UIAlertController: 0x10243b9b0>
Textfield warning in log:
2017-04-20 16:00:56.105536-0500 HAEForMe[52504:6463817] [MC] System group container for systemgroup.com.apple.configurationprofiles path is /Users/andrewzimmerman/Library/Developer/CoreSimulator/Devices/476B95C5-691C-43D0-98D8-EAC400F6A41A/data/Containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles
2017-04-20 16:00:56.106290-0500 AppName[52504:6463817] [MC]
Code:
//Begin UITextField Initialization
var inputFields = [UITextField]()
let textField = UITextField() // This continues until there are 10.
/*
AddFields - Adds UITextFields to the inputFields array.
*/
private func addFields()
{
inputFields.append(textField) // Done for all 10 fields.
}
/*
ViewDidLoad - Runs stated code when the view loads.
*/
override func viewDidLoad() {
super.viewDidLoad()
prepareScrollView() //If view is touched, keyboard is dismissed.
addFields() //Adds some UITextFields to an array.
managePages() //Adds images, textfields, and button to ScrollView
addDoneButtonOnKeyboard() //Adds a tool bar with the done button
}
/*
ManagePages - Adds all items required for Scroll View [Some code
is removed as it is irrelevant]
*/
private func managePages()
{
self.sideScrollView.frame = CGRect(x:0, y:0, width:self.view.frame.width, height:self.view.frame.height)
let scrollViewWidth:CGFloat = self.sideScrollView.frame.width
let scrollViewHeight:CGFloat = self.sideScrollView.frame.height
//*!*!* EFFICIENCY IN QUESTION *!*!*
for (index,element) in inputFields.enumerated()
{
if(index == 0)
{
//Sets properties of UITextField
}
else if(index == 1)
{
//Sets properties of UITextField
}
// The else if statements continue on until index is 9
}
let getStartedButton = UIButton(frame: CGRect(x:((scrollViewWidth*5) + scrollViewWidth/2)-125, y: scrollViewHeight-175, width: 250, height: 99))
getStartedButton.setImage(UIImage(named: "GetStartedButton"), for: UIControlState.normal)
getStartedButton.addTarget(self, action: #selector(finishSetup), for: .touchUpInside)
//*!*!* EFFICIENCY IN QUESTION *!*!*
for fieldName in inputFields
{
self.sideScrollView.addSubview(fieldName)
}
self.sideScrollView.addSubview(getStartedButton)
self.sideScrollView.contentSize = CGSize(width:self.sideScrollView.frame.width * 6, height:self.sideScrollView.frame.height)
self.sideScrollView.delegate = self
}
/*
FinishSetup - Checks all fields for input, ensure they aren't blank
and uses them
*/
func finishSetup(sender: UIButton!) {
print("Finished setup button tapped, attempting to finalize setup.")
var firstName: String = ""
var lastName: String = ""
var dateOfBirthValue: String = ""
var dateFormatter: DateFormatter
var date: Date = Date()
var age:Double = 0.0
var weight: Double = 0.0
var height: String = ""
var factType: String = "" //Name Changed
var valueType: Int = 0 //Name Changed
for (index,fieldName) in inputFields.enumerated()
{
if(fieldName.text != " " && fieldName.text != "" && fieldName.text != nil)
{
/*
Goes through each field in the array via each and every index
and then uses if statements to pull the text value for use in
a database later on.
*/
}
else
{
//*!*!* EFFICIENCY IN QUESTION *!*!*
print("Presenting blank field alert.")
let alertController = UIAlertController(title: "Uh Oh!", message: "Looks like you left something blank, please make sure that you've filled every field out and try again.", preferredStyle: UIAlertControllerStyle.alert)
let okayAction = UIAlertAction(title: "Okay", style: UIAlertActionStyle.default) {
(result: UIAlertAction) -> Void in
print("User reacted to blank field alert.")
}
alertController.addAction(okayAction)
self.present(alertController, animated: true, completion: nil)
return
}
}
}
Related
I'm trying to add two numbers in Swift 5, and I want to add some error checks. I don't want to make it possible for a user to click on the plus button if both of the text fields are not filled in. I tried with the if state below but it did not work.
the whole function:
#IBAction func sum(_ sender: Any) {
let one = input1.text
let oneInt = Int(one!)
let two = input2.text
let twoInt = Int(two!)
let total = oneInt! + twoInt!
label.text = "\(total)"
if(input2.text == nil){
addBtn.isEnabled = false
}
if(input1.text == nil){
addBtn.isEnabled = false
}
}
Try to use guard like this. If your input field does not contain any value that field return blank string and when you try to get integer value from that string it will return nil and your add button will be disable.
#IBAction func sum(_ sender: Any) {
guard let text1 = input1.text, let intValue1 = Int(text1) else {
addBtn.isEnabled = false
return
}
guard let text2 = input2.text, let intValue2 = Int(text2) else {
addBtn.isEnabled = false
return
}
label.text = "\(intValue1 + intValue2)"
}
A nice and simple way is to addTarget to your textFiels. This will enable you to handle the events on the text field. In this scenario we'll use .editingChanged and use a single selector to achieve our goal:
What we'll do : We will listen for when someone types something in the textfield. Whenever a text changed was made, we'll check to see if all the textfields was populated and then if it was we enable the sum button.
A small controller sample :: Make sure to read the comments to understand the code faster
import UIKit
class ViewController: UIViewController {
#IBOutlet var textfield1: UITextField!
#IBOutlet var textfield2: UITextField!
#IBOutlet var sumButton: UIButton!
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
sumButton.isEnabled = false /// Disable the button first thing
[textfield1, textfield2].forEach {
$0.addTarget(self, action: #selector(editingChanged(_:)), for: .editingChanged) /// add targets to handle the events (in your case it listens for the 'editingChanged' event )
}
}
#objc func editingChanged(_ textField: UITextField) {
/// Here we just loop through all our textfields
for each in [textfield1, textfield2] {
if let text = each?.text { /// Just make sure the textfields text is not nil
if text.count < 1 {
// If the textfiels text has no value in, then we keep the button disabled and return
sumButton.isEnabled = false
return
}
} else {
/// Else if the text field's text is nill, then return and keep the button disabled
sumButton.isEnabled = false
return
}
}
sumButton.isEnabled = true /// If the code reaches this point, it means the textfields passed all out checks and the button can be enabled
}
#IBAction func sum(_ sender: Any) {
let one = textfield1.text!
let two = textfield2.text!
guard let oneInt = Int(one), let twoInt = Int(two) else {
print("Whatever was in that text fields, couldn't be converted to an Int")
label.text = "Be sure to add numbers."
return
}
let total = oneInt + twoInt
label.text = "\(total)"
}
}
textfields are not nil but empty strings. so make your comparison like :
if input1.text == "" {
// do your check here
}
Seems like you want to start with addBtn.isEnabled = false then update it whenever the user enters two valid integers into the text fields, i.e. Int(input1.text ?? "") != nil && Int(input2.text ?? "") != nil. You can do this by adding a target to your textfields (input1 and input2) for .editingChanged events. For example, if you're doing this in a UIViewController, you can do this in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
addBtn.isEnabled = false
input1.addTarget(self, action: #selector(textFieldDidEdit(_:)), for: .editingChanged)
input2.addTarget(self, action: #selector(textFieldDidEdit(_:)), for: .editingChanged)
}
Where textFieldDidEdit(_:) action looks like:
#objc func textFieldDidEdit(_ sender: UITextField) {
addBtn.isEnabled = Int(input1.text ?? "") != nil && Int(input2.text ?? "") != nil
}
Finally your sum function becomes:
#IBAction func sum(_ sender: UIButton) {
guard let oneInt = Int(input1.text ?? ""), let twoInt = Int(input2.text ?? "") else {
return
}
let total = oneInt + twoInt
label.text = "\(total)"
}
As all of the number validation has moved to the textFieldDidEdit(_:) function.
Using material-components/material-components-ios v85.8.0
import MaterialComponents
....
var usernameTextField = MDCTextField()
var userNameTextLayout = MDCTextInputControllerUnderline()
usernameTextField = {
let usernameTextEdit = MDCTextField()
usernameTextEdit.translatesAutoresizingMaskIntoConstraints = false
usernameTextEdit.clearButtonMode = .unlessEditing
usernameTextEdit.backgroundColor = .white
return usernameTextEdit
}()
userNameTextLayout.textInput = usernameTextField
userNameTextLayout.placeholderText = "Username"
// add to view
....
private func isUserNameValid() -> Bool {
let enteredUsername = usernameTextField.text ?? ""
if (!enteredUsername.isValidEmail) {
userNameTextLayout.setErrorText("Invalid e-mail address",
errorAccessibilityValue: nil)
return false
}
}
Error messages cover the text entered and it looks bad:
Android material design does however place the error underneath the line:
Was wondering if there's a way to do that, or if I'm doing it wrong.
I followed their tutorial: https://codelabs.developers.google.com/codelabs/mdc-101-swift/#2
I had set the height of the MDCTextField too small. In increasing it from 50->80pts it did the trick and moved the error message below the line.
I have an alert box that takes user input and starts a download task. After the user clicks "Ok," I want the screen to show a UIView that I added an ActivityIndicator to while the download occurs. The download occurs successfully and the function correctly opens up the next controller, however, the custom view nor activity indicator are ever displayed. Here's my code:
private func getKeyFromAlert() {
let alert = UIAlertController(title: "Enter Key", message: "Enter your text key below. If you want to scan it instead, click \"Scan Key.\" ", preferredStyle: .alert)
alert.addTextField { (textField) in
let attribString = NSAttributedString(string: "Enter your app code here")
textField.attributedPlaceholder = attribString
}
let scanAction = UIAlertAction(title: "Scan Key", style: .default) { _ in
self.openSettingsForApp()
}
let okAction = UIAlertAction(title: "OK", style: .default) { _ in
let textField = alert.textFields![0]
if let text = textField.text {
let encoded = text.toBase64()
let status = APIKeychain.storeToken(encoded)
if !status {
self.displayAlertForBadKeychain(useCamera: false)
} else {
self.addLoader()
self.dismissSetup()
}
} else {
let _ = APIKeychain.storeToken("")
self.addLoader()
self.dismissSetup()
}
}
alert.addAction(scanAction)
alert.addAction(okAction)
show(alert, sender: nil)
}
The function in question that I use to display the UIView is addLoader()
The code for addLoader is:
private func addLoader() {
let frame = CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height)
let loaderView = UIView(frame: frame)
loaderView.alpha = 1.0
loaderView.backgroundColor = UIColor.white
let activitySpinner: UIActivityIndicatorView = UIActivityIndicatorView(frame: loaderView.frame)
activitySpinner.center = loaderView.center
activitySpinner.hidesWhenStopped = false
activitySpinner.style = .whiteLarge
activitySpinner.startAnimating()
loaderView.addSubview(activitySpinner)
self.view.addSubview(loaderView)
self.view.layoutIfNeeded()
}
I've tried several iterations of setNeedsDisplay and setNeedsLayout without luck. I've also tried explicitly declaring this in a DispatchQueue.main.async without any affect as well.
EDIT
I added the code below for dismissSetup()
private func dismissSetup() {
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
DispatchQueue.global(qos: .background).async {
if self.updateDatabase {
//This will start the download process
let _ = WebDataFetcher(dataStack: self.dataStack)
}
dispatchGroup.leave()
}
dispatchGroup.wait()
let mainSB = UIStoryboard(name: "UserInputs", bundle: nil)
let mainVC = mainSB.instantiateViewController(withIdentifier: "userInputs")
appDelegate.window?.rootViewController = mainVC
}
In my app, there is an add button, when clicked will add a new tableviewcell. However the problem is I am getting duplicate cells after the first time. Essentially, I am getting 2 duplicates when I click second time, 3 on third and so on.... Please see the images for the screenshot:
The above image is the first screen. Clicking '+' will add a new cell, which looks like the below images:
Now after saving,
Now if I go out of this view and come back, click add again and create a new cell, it gives me duplicates as mentioned in the above paragraphs (that is, 2 for second time, 3 for third time and so on...).
Here is the screenshot:
Here is the code I am using:
#IBAction func addBasket(_ sender: UIButton) {
let prefs = UserDefaults.standard
let status = prefs.string(forKey: "status")
if(status != nil){
if( status == "pending"){
if(BasketItemList.count < 5 ) {
self.addpost()
} else {
let alert = SCLAlertView()
alert.showWarning("Basket", subTitle: "Upgrade to add more")
}
} else {
if(BasketItemList.count < 50 ) {
self.addpost()
} else {
let alert = SCLAlertView()
alert.showWarning("Basket", subTitle: "Upgrade to add more")
}
}
}
}
func addpost() {
let appearance = SCLAlertView.SCLAppearance(
showCloseButton: false
)
let alert = SCLAlertView(appearance : appearance)
let txt = alert.addTextField("Enter name")
alert.addButton("Save") {
if txt.text?.characters.count != 0 {
let basketname : String = txt.text!
let userID = FIRAuth.auth()?.currentUser?.uid
let postitem : [String :AnyObject] = ["userid" : userID! as AnyObject , "basketname" : basketname as AnyObject ]
let dbref = FIRDatabase.database().reference()
dbref.child("BasketList").childByAutoId().setValue(postitem)
self.Basketdata2()
let appearance = SCLAlertView.SCLAppearance(
kDefaultShadowOpacity: 0,
showCloseButton: false
)
let alertView = SCLAlertView(appearance: appearance)
alertView.showTitle(
"Saved", // Title of view
subTitle: "", // String of view
duration: 2.0, // Duration to show before closing automatically, default: 0.0
completeText: "Done", // Optional button value, default: ""
style: .success, // Styles - see below.
colorStyle: 0xA429FF,
colorTextButton: 0xFFFFFF
)
} else {
let alert = SCLAlertView()
alert.showError("Oops!", subTitle: "Basket name should not be empty")
self.tableviewbasket.reloadData()
}
}
alert.addButton("Cancel"){
}
alert.showEdit("Add basket", subTitle: "Please enter your basket name")
}
func Basketdata2() {
HUD.show(.labeledProgress(title: "Loading...", subtitle: ""))
let databaseref = FIRDatabase.database().reference()
var userID = FIRAuth.auth()?.currentUser?.uid
if userID == nil {
userID = userfbid
}
databaseref.child("BasketList").queryOrdered(byChild: "userid").queryEqual(toValue: userID!).observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists() {
self.tableviewbasket.backgroundView = nil;
HUD.hide()
} else {
HUD.hide()
self.tableviewbasket.setContentOffset(CGPoint(x : 0, y: -98), animated: true)
if (self.BasketItemList.count == 0) {
// tableView is empty. You can set a backgroundView for it.
let label = UILabel(frame: CGRect(x: 5, y: 0, width: self.tableviewbasket.bounds.size.width, height:self.tableviewbasket.bounds.size.height))
label.text = "The best preparation for tomorrow \n is doing your best today.\n Please create your first basket."
label.textColor = UIColor.black;
label.textAlignment = .center
label.numberOfLines = 4
label.sizeToFit()
label.font = UIFont(name: "AvenirNext-Regular", size: 16.0)
self.tableviewbasket.backgroundView = label;
self.tableviewbasket.separatorStyle = .none;
}
}
})
}
func Basketdata() {
HUD.show(.labeledProgress(title: "Please wait...", subtitle: ""))
self.BasketItemList.removeAll()
self.Basketid.removeAll()
let databaseref = FIRDatabase.database().reference()
let userID = FIRAuth.auth()?.currentUser?.uid
databaseref.child("BasketList").queryOrdered(byChild: "userid").queryEqual(toValue: userID!).observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists() {
databaseref.child("BasketList").queryOrdered(byChild: "userid").queryEqual(toValue: userID!).observe(.childAdded, with: {
(snapshot) in
if let dictionary = snapshot.value as? [String : AnyObject] {
let basketitem = BasketList(text : "")
basketitem.setValuesForKeys(dictionary)
self.BasketItemList.append(basketitem)
self.Basketid.append(snapshot.key)
DispatchQueue.main.async {
if !self.BasketItemList.isEmpty {
HUD.hide()
self.tableviewbasket.reloadData()
}
}
} else {
self.tableviewbasket.reloadData()
HUD.hide()
}
})
} else {
if (self.BasketItemList.count == 0) {
// tableView is empty. You can set a backgroundView for it.
let label = UILabel(frame: CGRect(x: 5, y: 0, width: self.tableviewbasket.bounds.size.width, height:self.tableviewbasket.bounds.size.height))
label.text = "The best preparation for tomorrow \nis doing your best today"
label.textColor = UIColor.black;
label.textAlignment = .center
label.numberOfLines = 2
label.sizeToFit()
label.font = UIFont(name: "AvenirNext-Regular", size: 16.0)
self.tableviewbasket.backgroundView = label;
self.tableviewbasket.separatorStyle = .none;
}
HUD.hide()
}
})
}
Can someone help me understand what is wrong with my code? Thanks!
Edit: I have referred this thread without any luck: Getting duplicate cells with UITableViewController cellForRowAtIndexPath
Edit 2: Also, when I come out of that view and go to the same view, the duplicate values are vansihed.
Edit 3: Tried the answer without any success.
Follow below steps:
When you'r getting data from firebase db, first remove your array all objects that you'r using in the cellForRow method. In your case i think it should be array of Bucket (not sure).
Assign data to your object
reload tableview.
Reason of replication of data.
let your bucket have 2 values and it is stored inDB. When you fetch data from DB it gives you all the values i.e. 1,2,3. Now you adds these now your array will be 1,2,1,2,3.
Thats what happening in your case.
This question already has answers here:
Conditional Segue in Swift
(2 answers)
Closed 7 years ago.
I am creating an app. In a certain view, there are several text fields in which the user inputs numbers, then presses a button, and is transported into the next view controller.
How do I make it so if a person taps the button, and one or more text fields are empty, the segue cancels?
Here is my code for that area so far:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if saveButton === sender {
let name = nameTextField.text ?? ""
// Set the meal to be passed to MealListTableViewController after the unwind segue.
meal = Meal(name: name)
}
if calcButton === sender {
if weightInKilos.text == "" && percentOfDehydration.text == "" && ongoingLosses.text == "" && factor.text == "" {
let alertController = UIAlertController(title: "Fields were left empty.", message:
"You left some fields blank! Please make sure that all fields are filled in before tapping 'Calculate'.", preferredStyle: UIAlertControllerStyle.Alert)
alertController.addAction(UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.Default,handler: nil))
self.presentViewController(alertController, animated: true, completion: nil)
}
else {
let DestViewController: ftrViewController = segue.destinationViewController as! ftrViewController
let weightInt: Int? = Int(weightInKilos.text!)
let dehydrationInt: Int? = Int(percentOfDehydration.text!)
let lossesInt: Int? = Int(ongoingLosses.text!)
let factorInt: Int? = Int(factor.text!)
let lrs24Int = (30 * weightInt! + 70) * factorInt! + weightInt! * dehydrationInt! * 10 + lossesInt!
let lrsPerHourint = lrs24Int / 24
DestViewController.lrsHr = "\(lrsPerHourint)"
DestViewController.lrs24Hrs = "\(lrs24Int)"
}
}
}
You must check everything you need BEFORE calling the segue.
So, the user write the answers, you check if everything is ok, IF, its ok, do the segue, else, you alert the user and ask for retype the info.
Got it?