0

I want to create a function that can check whether or not a person has entered their middle name or if the variable is 'empty'(i.e nil). If it isn't empty it's going to essentially print the persons first, middle and full name, if not it's supposed to ignore the middle name and print out the first and last name if the value is 'nil'(which is is). For whatever reason it's not printing out person2FirstName and person2LastName without the middle name(which is what I want it to do).

I'm doing this to get practice using optionals in Swift. I'm having a TERRIBLE time wrapping my head around it in practice, though I understand that it's a way to safeguard your code in theory. I would appreciate any help you can give.

let person2FirstName: String =  "Steve"
let person2MiddleName: String? = "nil"
let person2LastName: String =  "Jones"

if person2MiddleName != nil {
"\(person2FirstName) \(person2MiddleName) \(person2LastName)"
} else {
"\(person2FirstName) \(person2LastName)"
}

I keep getting these errors:

main.swift:14:23: warning: string interpolation produces a debug description for an optional value; did you mean to make this explicit?
"\(person2FirstName) \(person2MiddleName) \(person2LastName)"
                      ^~~~~~~~~~~~~~~~~~~
main.swift:14:24: note: use 'String(describing:)' to silence this warning
"\(person2FirstName) \(person2MiddleName) \(person2LastName)"
                      ~^~~~~~~~~~~~~~~~~~
                       String(describing:  )
main.swift:14:24: note: provide a default value to avoid this warning
"\(person2FirstName) \(person2MiddleName) \(person2LastName)"
                      ~^~~~~~~~~~~~~~~~~~
                                         ?? <#default value#>
main.swift:14:1: warning: string literal is unused
"\(person2FirstName) \(person2MiddleName) \(person2LastName)"
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
main.swift:16:1: warning: string literal is unused
"\(person2FirstName) \(person2LastName)"
Anna Gibson
  • 107
  • 9
  • 2
    One of your issues is that `let person2MiddleName: String? = "nil"` is assigning a non-nil string that happens to contain the string "nil". You want to assign `... = nil` without the quotes – dan Apr 25 '18 at 19:10
  • Strongly related: [When should I compare an optional value to nil?](https://stackoverflow.com/questions/29717210/when-should-i-compare-an-optional-value-to-nil). – Martin R Apr 25 '18 at 19:11
  • /rant/ What pisses me off about the "duplicate" tag is that it's not necessarily. It was asked 2.5 years ago when Swift was 2 major versions back. Some of the methods shown in examples there aren't even correct anymore. SO is dying because it's afraid to revisit old topics and keep things fresh. The only thing worse than having to figure out how current an answer on the internet is just flat out not having any current answers because moderators decided we've addressed that already. /done rant/ – Travis Griggs Apr 25 '18 at 21:03

2 Answers2

2

Short answer:

Use if let instead of testing against nil. This "unwraps" the variable so that it's no longer Optional.

Instead of:

if foo != nil {
    doSomethingWith(foo) // WARNING: foo is optional!
}

Do this:

if let unwrappedFoo = foo {
    doSomethingWith(unwrappedFoo) // No warnings!
}

Long answer:

You seem to have a lot of variables in play here, many of which are logically grouped. Person1FirstName, Person1MiddleName, Person1LastName, Person2FirstName, ... Person3FirstName, ... etc. You can simplify this a lot by grouping these together in a struct, like so:

struct Person {
    let firstName: String
    let middleName: String?
    let lastName: String
}

Now we can add a convenient initializer, and a handy property to generate the full name:

struct Person {
    let firstName: String
    let middleName: String?
    let lastName: String

    init(firstName: String, middleName: String? = nil, lastName: String) {
        self.firstName = firstName
        self.middleName = middleName
        self.lastName = lastName
    }

    var fullName: String {
        if let middleName = self.middleName {
            return "\(self.firstName) \(middleName) \(self.lastName)"
        } else {
            return "\(self.firstName) \(self.lastName)"
        }
    }
}

(The initializer isn't strictly necessary, since the compiler will automatically generate one for us, but with the manually written one, we can default middleName to nil, which saves us from having to actually specify that when creating a Person with no middle name)

Then you can just:

let person = Person(firstName: "Joe", lastName: "Blow")
print(person.fullName) // outputs "Joe Blow"

The nice thing about this is that later on, if we need to, we can add additional features to the Person struct without breaking existing code that uses it. For example, suppose we want to add the ability to handle really long names with more than one middle name in them. We can just replace middleName with a middleNames array property, and then do something like this:

struct Person {
    let firstName: String
    let middleNames: [String]
    let lastName: String

    init(firstName: String, middleName: String? = nil, lastName: String) {
        // continues to work exactly as before!
        self.firstName = firstName
        // map on an optional just means "run the closure if the value's not nil."
        // We could also use if let instead.
        self.middleNames = middleName.map { [$0] } ?? []
        self.lastName = lastName
    }

    init(firstName: String, middleNames: [String], lastName: String) {
        // new initializer that takes multiple middle names
        self.firstName = firstName
        self.middleNames = middleNames
        self.lastName = lastName
    }

    var fullName: String {
        let names = [self.firstName] + self.middleNames + [self.lastName]

        return names.join(separator: " ")
    }

    // and so that old code that called the `middleName` property continues to work:
    var middleName: String? { return self.middleNames.first }
}
Charles Srstka
  • 16,665
  • 3
  • 34
  • 60
0

For this case, I'd use ??. It allows you to access an optional value OR an alternative value if the optional is currently nil.

let first:String =  "Steve"
let middle:String? = nil  // fixed this
let last:String =  "Jones"

let fullName = "\(first) \(middle ?? "") \(last)"

The only problem with this is that you end up with double space when the person has no middle name. A better solution in that case would be something like:

let fullName = [first, middle ?? "", last].filter( { !$0.isEmpty} ).joined(separator: " ")
Travis Griggs
  • 21,522
  • 19
  • 91
  • 167