When I press a button in my app ,the whole app freezes - ios

My app has 3 components:
A textfield where the user enters a number to see if the number is prime or not
A button that when pressed does an action to determine if the number is prime or not
A label that shows whether the number is prime or not
When the button is pressed, the whole app freezes(but does not crash)and I have no idea why. I tried searching about this online, but none have solved my problem.Is there anything I can check for errors, or what reasons can a button freeze an app.
Here is my code:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var text: UITextField!
#IBOutlet weak var label: UILabel!
var i = 2
#IBAction func buttonPressed(_ sender: Any) {
if let userEnteredText = text.text{
var isPrime = true
let number:Int = Int(userEnteredText)!
while i < number{
if number % i == 0{
isPrime = false
i = i + 1
}
if isPrime == true{
label.text = "\(number) is Prime"
label.textColor = UIColor.black
}else{
label.text = "\(number) is not Prime"
label.textColor = UIColor.black
}
}
}else{
label.text = "Error-Enter a positive integer"
label.textColor = UIColor.red
}
}
}

Because the i++ should be outside of the if statement
...
while i < number{
i = i + 1
if number % i == 0 {
isPrime = false
}
...
Please note : Since you are trying to compute something that can take sometimes, you'll have(when you learn a little more) to do this kind of computation outside of the main thread. Your apps locks because you are blocking the main thread which is the one controlling the UI/touch/interaction of your app. Look into dispatch_async
PPS : Learn how to point break point in XCode and step over/into, you'll be able to debug the flow of your app.

Related

How to move function with button sender to a separate file?

I have a difficulty in a simple task. I googled this topic, but other examples are complicated by additional syntax that I don't understand yet. Can you help me to solve it or give link if there is already was similar topic.
I need to move the function responsible for selecting the button to a separate file, because if the number of buttons increases, it will turn into a large sheet. So made a function in separate swiftfile, but naturally the new file does not know about any buttons in viewController and can't find it in scope. Also If i’m not mistaken i need give Bool and return String.
How can I transfer a function with button sender to a separate file so that it returns non-optional text value back in the ViewController?
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var Label1: UILabel!
#IBOutlet weak var BTC: UIButton!
#IBOutlet weak var ETC: UIButton!
#IBOutlet weak var LTC: UIButton!
var choice = "Choose coin"
var coinType = getCoinType()
#IBAction func optionSelected(_ sender: UIButton) {
BTC.isSelected = false
ETC.isSelected = false
LTC.isSelected = false
sender.isSelected = true
if BTC.isSelected == true{
ETC.isSelected = false
LTC.isSelected = false
choice = "BTC"
Label1.text = choice
}else if ETC.isSelected == true{
BTC.isSelected = false
LTC.isSelected = false
choice = "ETC"
Label1.text = choice
}else if LTC.isSelected == true{
ETC.isSelected = false
LTC.isSelected = false
choice = "LTC"
Label1.text = choice
}
}
}
new file, i can't understand how to get sender from buttons here
import Foundation
func getCoinType() -> String{
var choice: String
// my if else function
return choice
}
P.S. In general, is it possible to make it easier, without using UIPickerView?
On how to add functions in different swift file you have a few options. This a simple one: Create a new swift file and name it ViewController+Extenstions.swift (you can use any name though). Then add an extension to your ViewController class and add your function like this:
extension ViewController {
func getCoinType() -> String {
var choice: String
// my if else function
return choice
}
}
You can add as many functions as you need and in different files (all extensions to ViewController but of course with different file names).
PS, in your optionSelected function, you are setting:
BTC.isSelected = false
ETC.isSelected = false
LTC.isSelected = false
I don't know what you're trying to achieve, but by doing so, those if-else will never be executed since your are setting the three buttons as not-selected!
UPDATE:
For your button selection problem, you can do this:
1- Add tags to your buttons
You can do it inside viewDidLoad method. This way you can differentiate between different buttons.
override func viewDidLoad() {
super.viewDidLoad()
BTC.tag = 0
ETC.tag = 1
LTC.tag = 2
}
2- Connect the action of buttons
Although your three buttons can have three different handler functions, it's easier to connect all of them to a single handler. However, we can know which button is tapped based on the value of the tag we assign in the previous step. So, connect all buttons to optionSelected(_ sender: UIButton) through Storyboard (because you have used Storyboard.
3- Rewrite getCoinType function
func getCoinType(tag: Int) -> String? {
var choice: String?
switch tag {
case 0:
choice = "BTC"
case 1:
choice = "ETC"
case 2:
choice = "LTC"
default:
choice = nil
}
return choice
}
Buttons' handler
Now when a button is tapped we call getCoinType function with that button's tag as input argument. It will return the string and we assign it to the Label1:
#IBAction func optionSelected(_ sender: UIButton) {
let choice = getCoinType(tag: sender.tag)
Label1.text = choice
}
And you're done!

Swift: one button to alternate multiple test message in UIView

I am trying to implement one button- one click to display the first message, the second click would change to second message, the third click would change to the third message.
I looked up to one possible solution is to use UITapGestureRecognizer - single tap and double tap, which means I can trigger the button (single tap to display the first message and double tap to display the second message).
However, if I have more than two lines and I just want to display them by clicking each one (like animation). Would that be possible to just deal with that inside one UIView and UIbutton?
I currently have 3 simple messages:
#IBOutlet weak var Textfield: UITextView!
#IBOutlet weak var Changingbutton: UIButton!
#IBAction func ChangingTapped(_ btn: UIButton) {
Textfield.text = "Changing to driving"
Textfield.text = "Changing to walking"
Textfield.text = "Changing to cycling"
}
The problem now is when I click the button it would just go the last message. That might not be a clever way to do so.
Thanks so much for the inputs and I am sorry if that is a rather simple question.
You can implement a custom CaseIterable enumeration to perform a loop so that you can get the next element every time you press a button:
extension CaseIterable where Self: Equatable {
var allCases: AllCases { Self.allCases }
var nextCase: Self {
let index = allCases.index(after: allCases.firstIndex(of: self)!)
guard index != allCases.endIndex else { return allCases.first! }
return allCases[index]
}
#discardableResult
mutating func next() -> Self {
self = nextCase
return self
}
}
Create a enumeration with your transportation modes:
enum Mode: String, CaseIterable {
case cycling, driving, walking
}
add a mode property to your view controller and set the initial value
var mode: Mode = .cycling
Now you can simply call the next mode method every time you press the button:
func ChangingTapped(_ btn: UIButton) {
Textfield.text = "Changing to " + mode.next().rawValue
}
Note: It is Swift naming convention to name your methods and properties starting with a lowercase letter.
Why not just set a counter and increment it every time your IBAction is activated?
var x = 0
#IBAction func ChangingTapped(_ btn: UIButton) {
if(x==0){
Textfield.text = "Changing to driving"
}
else if(x==1){
Textfield.text = "Changing to walking"
}
else{
Textfield.text = "Changing to cycling"
}
x +=1
//if x needs to be reset
/*
if(x > 2) x = 0
*/
}

How to go back when the UITextfield is empty in Swift code?

My question is: When the UITextField is empty, how do I click the "Backspace" button to go to the previous UITextField? I have been struggling trying to do this in my code below?
Second Question: How do I only allow 1 character to get entered in the UITextField?
I am new at Swift code and trying to learn. Any help would be great.
What I am trying to do is have the user be able to type in a code in the 6 UITextFields and be able to click the "Backspace" button on any one of the UITextFields with only allowing the user to enter one number in each UITextField.
Code Below:
#objc func textFieldDidChange(textfield: UITextField) {
let text = textfield.text!
if text.utf16.count == 0 {
switch textfield {
case textField2:
textField1.becomeFirstResponder()
textField1.backgroundColor = UIColor.blue
textField1.tintColor = .clear
case textField3:
textField2.becomeFirstResponder()
textField2.backgroundColor = UIColor.blue
textField2.tintColor = .clear
case textField4:
textField3.becomeFirstResponder()
textField3.backgroundColor = UIColor.blue
textField3.tintColor = .clear
case textField5:
textField4.becomeFirstResponder()
textField4.backgroundColor = UIColor.blue
textField4.tintColor = .clear
case textField6:
textField5.becomeFirstResponder()
textField5.backgroundColor = UIColor.blue
textField5.tintColor = .clear
textField6.resignFirstResponder()
textField6.backgroundColor = UIColor.blue
textField6.tintColor = .clear
default:
break
}
}
else if text.utf16.count == 1 {
switch textfield {
case textField1:
textField1.backgroundColor = UIColor.black
textField1.textColor = .white
textField1.tintColor = .clear
textField2.becomeFirstResponder()
textField2.backgroundColor = UIColor.black
textField2.textColor = .white
textField2.tintColor = .clear
case textField2:
textField3.becomeFirstResponder()
textField3.backgroundColor = UIColor.black
textField3.textColor = .white
textField3.tintColor = .clear
case textField3:
textField4.becomeFirstResponder()
textField4.backgroundColor = UIColor.black
textField4.textColor = .white
textField4.tintColor = .clear
case textField4:
textField5.becomeFirstResponder()
textField5.backgroundColor = UIColor.black
textField5.textColor = .white
textField5.tintColor = .clear
case textField5:
textField6.becomeFirstResponder()
textField6.backgroundColor = UIColor.black
textField6.textColor = .white
textField6.tintColor = .clear
case textField6:
textField6.resignFirstResponder()
default:
break
}
}
}
I'd just like to point out that I'm still relatively new to iOS and Swift in general, but even with just a few minutes of searching, I was able to find some seeds of ideas which provided me with the suggested solution.
Based on your (improved) question, I believe a different approach is required. What you really don't want to use a text component. "Why"?
I here you ask. Because they don't actually provide you with the functionality that you want and come with a considerable overhead.
For this, what you really want is more control. You want to know when a key is pressed and you want to respond to it (I know, sounds like a text component, but) and be notified when more extended functionality occurs, like the delete key is pressed.
After a few minutes of research, some trial and error, I found that the UIKeyInput is more along the lines of what you want.
It will tell you when text is inserted and, more importantly, will tell you when Delete is pressed
The added benefit is, you can filter the input directly. You can take the first character from the String and ignore the rest or auto fill the following elements with the remaining text. You can perform validation (for numerical only content) and what ever else you might want to do
So, I started a really new project, added a UILabel to the UIViewController in the storyboard, bound it to the source and implemented the UIKeyInput protocol as such...
class ViewController: UIViewController {
override var canBecomeFirstResponder: Bool {
return true
}
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
becomeFirstResponder()
}
}
extension ViewController: UIKeyInput {
var hasText: Bool {
return true
}
func insertText(_ text: String) {
print(text)
label.text = text
}
func deleteBackward() {
print("Delete backward")
}
}
I ran the project and when a key was typed, the label was updated with the new key and when delete was pressed, the Delete backward text was printed to console.
Now. You have some choices to make. To use a single UIViewController and (maybe) a series of UILabels and manage interactions within it, so when a key is typed, you present the next label as the input focus (and when delete is pressed, you move back) or do you create a series of UIControls which represent each digit and manage via some delegate call back process.
You may also need to implement the UITextInputTraits protocol, which will allow you to control the keyboard presented
You might also like to have a read through Responding to Keyboard Events on iOS, CustomTextInputView.swift and Showing the iOS keyboard without a text input which were just some of the resources I used to hobble this basic example together with.
you can use this extension for your second question:
import UIKit
private var maxLengths = [UITextField: Int]()
extension UITextField {
#IBInspectable var maxLength: Int {
get {
guard let length = maxLengths[self] else {
return Int.max
}
return length
}
set {
maxLengths[self] = newValue
addTarget(
self,
action: #selector(limitLength),
for: UIControlEvents.editingChanged
)
}
}
#objc func limitLength(textField: UITextField) {
guard let prospectiveText = textField.text,
prospectiveText.count > maxLength
else {
return
}
let selection = selectedTextRange
let maxCharIndex = prospectiveText.index(prospectiveText.startIndex, offsetBy: maxLength)
text = prospectiveText.substring(to: maxCharIndex)
selectedTextRange = selection
}
}
when you add this extension to your project you can see an extra attribute in "Attribute Inspector" tab and you can set the max length of UITextField.

Segue performing prematurely in if / else clause

The following code
class ViewController: UIViewController {
#IBOutlet weak var knock: UIButton!
#IBOutlet weak var label: UILabel!
var count: Int = 0
var unlocked: Int = 0
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 knockKnock(sender: UIButton) {
if count < 20 {
label.text = "Knocked \(count) times."
} else if count > 19 && count < 40 {
label.text = "Knocked \(count) times."
label.textColor = UIColor.randomColor()
} else if count > 39 && count < 100 {
label.text = "That's enough."
label.textColor = UIColor.blackColor()
} else if count > 99 && count < 102 {
label.text = "I wish you hadn't done that."
label.textColor = UIColor.redColor()
} else if count == 103 {
label.text = "Fine, come in."
label.textColor = UIColor.greenColor()
count = 126
unlocked += 1
}
if unlocked > 0 {
self.performSegueWithIdentifier("InsideHomeSegue", sender:sender)
}
count += 1
}
is prematurely performing the Segue in
if unlocked > 0 {
self.performSegueWithIdentifier("InsideHomeSegue", sender:sender)
}
The segue is performed immediately the first time the button is pressed, which I do not understand, as at that point the code should be unreachable behind a false if statement, since int unlocked is initialized with 0
I have had similar problems which turned out to be issues with XCode itself rather than my code; I have looked through all the files I can find referencing any variables or objects in Interface Builder, checked for naming inconsistencies, rogue outlets, etc.
I still have a very shaky understanding of Segues, and would appreciate any detailed help anyone can offer.
The larger if / else if block worked properly all the way up to the final increment until Segue code was added.
Originally the self.performSegueWithIdentifier("InsideHomeSegue", sender:sender) line was inside the else if count == 103 { block; var unlocked: Int was added to combat the Segue issue to no avail.
I've read through several pages of the XCode documentation on Segues, but haven't found anything else I can try.
It's possible I've overlooked something incredibly obvious, but regardless, thanks for reading.
Cheers
EDIT: I have checked all outlets, it's the only Segue in the project currently and it isn't hooked up anywhere else. The Segue is activated on a button press, not immediately on View load.
Just like what my comment said, You've connected the segue with your button, so that the segue will alway execute. It has nothing to do with if clause.
You should connect with the ViewController instead of connecting with a button
You have to connect your two ViewController with a segue, giving it an identifier. Then, you can trigger the segue in this way.
self.performSegueWithIdentifier("segue_name_identifier", sender: self)
Here how you can easily set your segue.
I hope that this will help you.

UIButton not reacting to first press in Swift

I am working on a simple (ok, it should be simple) calculator that estimates risk based on a few inputs (toggle on/off)
Everything is working properly, except that the UIButtons need to be tapped 2 times to toggle initially. After that, the UIButtons work as expected.
Here is the code (contained within viewDidLoad())
#IBOutlet weak var confusionButton: UIButton!
var counter = 0
var confusionToggle = 0
and here is the action
#IBAction func pressConfusion(sender: UIButton) {
if confusionToggle == 0 {
confusionButton.backgroundColor = UIColor.clearColor()
confusionButton.alpha = 1;
confusionToggle++;
counter++;
} else {
confusionButton.backgroundColor = UIColor.grayColor()
confusionButton.alpha = 0.5;
confusionToggle = 0
counter--;
}
}
Any thoughts on this one? My Swift skills are very limited (my first attempt with Swift)
Think your just getting yourself confused. Use print statements to first understand what is happening, then do all your fancy button UI changes.
#IBAction func tapButton(sender: UIButton) {
// user println to help debug
println(confusionToggle)
if confusionToggle == 0 {
sender.backgroundColor = UIColor.grayColor()
sender.alpha = 0.5;
confusionToggle++;
counter++;
}
else {
// RESET
sender.backgroundColor = UIColor.clearColor()
sender.alpha = 1;
confusionToggle = 0
counter--;
}
}
I don't know what you trying to do.
Just let you know that confusionToggle will be 0 at first time. So, when you click on the button your first condition will be called, It will clear the background color and increase the value of confusionToggle to 1 then after it's because of 1 it will not go to the first condition. So, it goes to else part and give the background color to the gray. confusionToggle again goes to 0.
And it will iterate every time like 0 - 1 - 0 - 1 ...
If you want to do gray color to the button when you tap then you have to change your logic.
if confusionToggle == 0 {
confusionButton.backgroundColor = UIColor.grayColor()
confusionButton.alpha = 0.5;
confusionToggle = 1;
counter++;
} else {
confusionButton.backgroundColor = UIColor.clearColor()
confusionButton.alpha = 1;
confusionToggle = 0
counter--;
}
Hope it helps to you.

Resources