It has been a long time since I have coded in iOS and I am upgrading the old app to swift 3. I am really struggling with one issue of using optional variables.
I have a textfield which is optional. I want it unwrapped into a non-optional Int so that I can use it in the other functions for calculation.
#IBOutlet weak var txtRuns: UITextField!
func sendScore()
{
let runs = txtRuns.text!
let overs = txtOvers.text!
let balls = txtBalls.text!
let wkts = txtWkts.text!
let target = txtTarget.text!
let totalOvers = txtTotalOvers.text!
let strData = "S|R\(runs)" + getOptionalScoreData(
runs: Int(runs),
wkts: Int(wkts),
overs: Int(overs),
balls: Int(balls),
target: Int(target),
totalOvers: Int(totalOvers)
)
}
func getOptionalScoreData(runs: Int, wkts: Int, overs: Int, balls: Int, target: Int, totalOvers: Int) -> String
{
if ( runs == 0 ) {
return getCurrentRunRate(runs: runs)
}
return "";
}
As you can see, I have so many functions to call and I want this textfield to turn into non-optional INT.
Now I have tried several options that I read over here but the error messages only change. The problem didn't solve.
Current ERROR is
The value of optional type 'Int?' not unwrapped; did you mean to use '!' or '?'?
Please help.
Thanks
.................
Please note that I don't think nested check is a nice idea here because all these variables are independent of each other. One can be nil but other can be still passed and used.
If I nest checks like this, it means that no other value will be passed if runs are nil.
if let runs = txtRuns.text, let runsInInt = Int(runs) {
if let overs = txtOvers.text, let oversInInt = Int(overs) {
if let wkts = txtWkts.text, let wktsInInt = Int(wkts) {
strData = "S|R\(runs)\(overs)\(wkts)" + getOptionalScoreData( runs: runsInInt, overs: oversInInt, wkts: wktsInInt)
}
If you are frequently getting value from UITextField as Int, you can add an extension as follows:
extension UITextField {
var intValue: Int {
get {
if let text = self.text {
return Int(text) ?? 0
}
return 0
}
set {
self.text = String(newValue)
}
}
}
You can add the above as private extension in your viewcontroller too. Now you can rewrite your code as:
func sendScore() {
let strData = "S|R\(txtRuns.intValue)\(overs.intValue)\(wkts.intValue)" + getOptionalScoreData(
runs: txtRuns.intValue,
wkts: wkts.intValue,
overs: overs.intValue,
balls: balls.intValue,
target: target.intValue,
totalOvers: totalOvers.intValue)
)
}
Int(runs) call constructor of Int following:
public init?(_ text: String, radix: Int = default)
Because String to Int might failed due to the String might not a valid integer.
How would you deal with it?
You can reference Sallu's comment.
user ! to guarantee the String in UITextField is absolute a valid integer, or app crash.
runs: Int(runs)!
user ?? to give a default value if the String in UITextField is not a valid integer.
runs: Int(runs) ?? 0
In the case the default value is 0
Related
I need to figure out a solution to prevent the app from crashing when a value is not entered in a textfield. The idea is if the textfield is empty (nil), the value of that textfield will then equal zero. Below I have copied my code:
let taxPercentDou:Double = Double(taxnosign2!)!
Anyone has any suggestions?
You need to stop force unwrapping optionals with the exclamation ! sign:
let b:Double = Double(rent.text!)!// Rent
If rent.text is nil or contains non-numeric text, your app will crash.
You can check for nil and replace it with "0" using the null coalescing operator:
rent.text ?? "0"
You can pass that into the Double initializer like this:
Double(rent.text ?? "0")
However, this will return nil if rent.text contains non-numeric text. You can use the null coalescing operator again to handle this:
let b: Double = Double(rent.text ?? "0") ?? 0.0
You could take this one step farther and write a simple utility method to make it easier to use for all your fields:
func convertToDouble(text: String?) -> Double {
return Double(text ?? "0") ?? 0.0
}
And call it like this:
let b = convertToDouble(rent.text)
Please find the below code. It Might Help you.
static func isNull(aString: AnyObject?) -> Bool
{
//Check for null
if(aString is NSNull){
return true
}
if(aString == nil){
return true
}
let x: AnyObject? = aString
//if string is nsnumber , convert it into string and check
if(aString is NSNumber){
var aString1 : String? = ""
aString1 = String(describing: aString)
return self.isStringEmpty(aString: aString1!)
}
if let aString1 = x as? String
{
return self.isStringEmpty(aString: aString1)
}
else
{
return true
}
}
I study swift. I have a question about initializers init().
For example, I want to initialize Int.
var number: Int = 20
var number = Int(20)
var number = Int.init(20)
All expression is same?
Second, Why this expression occurs?
var check = "123"
var phoneNum:Int?
if((phoneNum = Int.init(check)) != nil)
{
print("Success");
}
There is no error!
var check = "123"
var phoneNum:Int? = Int.init(check)
if(phoneNum != nil)
{
print("Success");
}
Yes, these all have the same effect:
var number: Int = 20
var number = Int(20)
var number = Int.init(20)
And this is one more way to do it:
var number = 20
This produces an error:
var check = "123"
var phoneNum:Int?
if((phoneNum = Int.init(check)) != nil)
{
print("Success");
}
You get an error (“error: value of type '()' can never be nil, comparison isn't allowed”) because assignment in Swift returns (), the sole value of type Void, but nil is type Optional, which is different than Void. Assignments in Swift cannot generally be used as expressions.
I wanted to add this as a comment to rob's answer but since I don't have enough reputation, here's my answer as? a comment (pun intended ;).
Regarding the last two examples you can also use optional binding to help in an assignment:
var check = "123"
var phoneNumber: Int?
if let number = Int.init(check) {
phoneNumber = number
print("Success")
}
print(phoneNumber)
// Success
// Optional(123)
Changing the check value:
var check = "A23"
var phoneNumber: Int?
if let number = Int.init(check) {
phoneNumber = number
print("Success")
}
print(phoneNumber)
// nil
I hope this helps too.
I have a weird issue trying to validate user input. I'm using a wrapper for a form framework and I want it to be able to validate user input.
The trouble is, when I call the closure with the userValue argument, it ends up being nil and all checks return false:
class FormRowWrap {
var tag: String
var row: AnyObject
var verification: (value: Any?) -> Bool
init(tag: String, row:AnyObject, verification:(Any?) -> Bool) {
self.tag = tag
self.row = row
self.verification = verification
}
}
class InsertViewController: FormViewController {
let rows = [
{
let tag = "Fuel Name"
let row = FormRowWrap(tag: tag,
row:TextRow(tag) {
$0.title = tag
// $0.value = last known user default
},
verification:({(value: Any?) -> Bool in
if let thing = value as? String {
//^----- the value in a **breakpoint here is nil**
//
if !thing.isEmpty {
return true
}
}
return false
}))
return row
}() as FormRowWrap,
{
let tag = "Price"
let row = FormRowWrap(tag: tag,
...
func formValuesAreValid(values: [String: Any?]) -> Bool {
var result = false
for rowWrap in self.rows {
let userValue = values[rowWrap.tag]
print("userValue \(userValue) forTag: \(values[rowWrap.tag])")
// ^---- this prints userValue **Optional(Optional("Ghh")) forTag: Optional(Optional("Ghh"))**
let entryIsValid = rowWrap.verification(value: userValue)
if (!entryIsValid) {
result = false
return result
}
}
result = true
return result
}
If I run rowWrap.verification(value:"test") it returns true, so I think it's an issue about properly unwrapping values.
Your function needs an Optional but userValue is an Optional inside another Optional:
Optional(Optional("Ghh"))
So when you force unwrap it with
let entryIsValid = rowWrap.verification(value: userValue!)
what happens actually is that you unwrap the first layer and get back an Optional:
Optional("Ghh")
which is what your function signature
(value: Any?) -> Bool
needs.
About why it's wrapped twice:
with
formValuesAreValid(values: [String: Any?])
the values in the values dictionary are Optionals, and then when you access the dictionary:
let userValue = values[rowWrap.tag]
you get yet another Optional - because accessing a dictionary always returns an Optional, so in this case values[rowWrap.tag] returns an "Optional Optional".
Then somewhere else you unwrap once thinking you'll get the value if let thing = value as? String but you get the inner Optional instead and your next check fails.
Found a way to make it work by force unwrapping the "userValue":
let entryIsValid = rowWrap.verification(value: userValue!)
I still don't understand why this works and why it doesn't work with the argument as wrapped optional.
I have been trying to fix all my code since swift 2.0 update. I have a problem that seems to be the way tuples work now:
public func generate() -> AnyGenerator <(String, JSON)> {
switch self.type {
case .Array:
let array_ = object as! [AnyObject]
var generate_ = array_.generate()
var index_: Int = 0
return anyGenerator{
if let element_: AnyObject = generate_.next() {
return ("\(index_++)", JSON(element_))
} else {
return nil
}
}
case .Dictionary:
let dictionary_ = object as! [String : AnyObject]
var generate_ = dictionary_.generate()
return anyGenerator{
if let (key_: String, value_: AnyObject) = generate_.next() {
return (key_, JSON(value_))
} else {
return nil
}
}
default:
return anyGenerator{
return nil
}
}
}
Specifically the line:
if let (key_: String, value_: AnyObject) = generate_.next()
Is throwing the error: Tuple pattern element label 'key' must be '_'
I tried to make that change already, but I didnt work...
Any ideas?
The problem is: We cannot use type annotation inside of tuple patterns anymore.
In the release notes:
Type annotations are no longer allowed in patterns and are considered part of the outlying declaration. This means that code previously written as:
var (a : Int, b : Float) = foo()
needs to be written as:
var (a,b) : (Int, Float) = foo()
if an explicit type annotation is needed. The former syntax was ambiguous with tuple element labels. (20167393)
So, you can:
if let (key_, value_): (String, AnyObject) = generate_.next() {
But in this case, you could omit : (String, AnyObject):
if let (key_, value_) = generate_.next() {
I have buttons that when pressed, will call/message a number from an array. i.e. button1 will call the number at index 0 of the array, button2 at index 1, etc.. For some reason whenever the number from the array contains a format other than xxx-xxx-xxx it crashes (i.e. (xxx) xxx-xxx). And yet, the log gives me the following error even though the array isn't nil:
Anyone know why this is happening?
Here is the code for everything:
import UIKit
import AddressBook
var contactInfo: [String] = []
[...]
override func viewDidLoad() {
super.viewDidLoad()
//this is the function that grabs the array from an app group
setUpCallMessageButtons()
[...]
callButton1.addTarget(self, action: "call:", forControlEvents: UIControlEvents.TouchUpInside)
}
func call(sender:UIButton!)
{
if (sender == callButton1) {
println("\(contactInfo)")
var url:NSURL? = NSURL(string: "tel:\(contactInfo[0])")
self.extensionContext?.openURL(url!, completionHandler:{(success: Bool) -> Void in
})
}
}
func setUpCallMessageButtons(){
let appGroupID = "**redacted**"
let defaults = NSUserDefaults(suiteName: appGroupID)
contactInfo = (defaults!.objectForKey("contactInfo") as! [String])
println("\(contactInfo)")
//This is gives the log down below. As you can see, none are nil.
}
Buttons 1,2 and 5 work while 3 and 4 always crash.
My guess is that if the phone number isn't formatted correctly, the call to convert it to an NSURL is failing and returning nil.
You probably need to wrap your call to openURL in an optional binding ("if let") block:
var url:NSURL? = NSURL(string: "tel:\(contactInfo[0])")
if let url = url
{
self.extensionContext?.openURL(url!,
completionHandler:
{
(success: Bool) -> Void in
}
}
else
{
println("Phone number \(contactInfo[0]) is not in a valid format")
}
You might want to strip away parenthesis from your phone number before trying to create your URL. A simple way would be to use the NSString method stringByReplacingOccurrencesOfString:withString:.
Here's a little storyboard - which shows you where the nil is coming from
Unexpectedly found nil means there is a variable which is expected to be non-nil but at run time was nil
This is the line of code that is causing the issue
self.extensionContext?.openURL(url!, completionHandler:{(success: Bool)
It expects url to be non-nil (i.e. the !) but it is definitely nil (see image)
If this data comes from the user or from the internet, you might want a method to strip away all non-numeric characters. Something like this (from a working playground I just banged out) :
import UIKit
func digitsOnly(#fromString: String) -> String
{
var workString = NSMutableString(string: fromString)
let digitsSet = NSCharacterSet.decimalDigitCharacterSet()
var index: Int
for index = count(fromString)-1; index>=0; index--
{
if !digitsSet.characterIsMember(workString.characterAtIndex(index))
{
workString.deleteCharactersInRange(NSRange(location:index, length:1))
}
}
return workString as String
}
let testString = "(555) 111-2222"
let result = digitsOnly(fromString:testString)
println("digitsOnly(\"\(testString)\") = \"\(result)\" ")
This displays:
digitsOnly("(555) 111-2222") = "5551112222"
Edit:
Or alternately a more Swift-like version of the same function:
func digitsOnly(#fromString: String) -> String
{
var result = String()
let digitsSet = NSCharacterSet.decimalDigitCharacterSet()
for char in fromString
{
if digitsSet.characterIsMember(char as unichar)
result += char
}
}
EDIT #2:
You can increase the set of characters that is left in place by changing the character set you use. Replace the line
let digitsSet = NSCharacterSet.decimalDigitCharacterSet()
With
let digitsSet = NSCharacterSet(charactersInString: "0123456789+-")
To preserve "+" signs and dashes. (Edit the string to include the characters you need.)