16

I have a the following name: John Fitzgerald Kennedy.

To get its initials, I created a method:

extension String {
    public var first: String {
        return String(self[startIndex])
    }
}

let initials = "John Fitzgerald Kennedy".componentsSeparatedByString(" ")
      .reduce("") { $0 + $1.first }

The output is : JFK

Is there an elegant way with my method (with reduce) to limit those initials to JK only, removing then the letter in the middle?

Nico
  • 6,269
  • 9
  • 45
  • 85
  • Note that this will break for many non english names. In Spanish names the last part of the name does not have to be the most important. And dont let me even start on asian names. If you need correct initials, you need the user to fill them in. – Sulthan Feb 13 '16 at 08:32
  • Yes I was thinking to give the user a way to change it. Thanks. – Nico Feb 13 '16 at 08:33

6 Answers6

43

If you target iOS 9 and above:

let formatter = PersonNameComponentsFormatter()
if let components = formatter.personNameComponents(from: name) {
     formatter.style = .abbreviated
     return formatter.string(from: components)
}
Cherpak Evgeny
  • 2,659
  • 22
  • 29
  • 4
    This should be the accepted answer! Thx for letting me know about `PersonNameComponentsFormatter` – laka Jan 31 '20 at 14:40
  • This is indeed the best way IMO! Important to note though, that according to [Apple’s docs](https://developer.apple.com/documentation/foundation/personnamecomponentsformatter/1642979-personnamecomponents): *Currently, only names using Latin or CJK scripts are supported.* – alexkaessner Jun 27 '23 at 13:28
24

Swift 2.0

How about this:

let initials = "John Fitzgerald Kennedy".componentsSeparatedByString(" ")
.reduce("") { ($0 == "" ? "" : $0.first) + $1.first}

That will also take care of the case where the name has no middle name or more than 1 middle name.

Swift 3

let initials = "John Fitzgerald Kennedy".components(separatedBy: " ").reduce("") { ($0 == "" ? "" : "\($0.characters.first!)") + "\($1.characters.first!)" }

Swift 4

let initials = "John Fitzgerald Kennedy".components(separatedBy: " ").reduce("") { ($0 == "" ? "" : "\($0.first!)") + "\($1.first!)" }
nCr78
  • 460
  • 7
  • 9
Guy Daher
  • 5,526
  • 5
  • 42
  • 67
  • Hi I'm getting the error: 'subscript' is unavailable: cannot subscript String with a CountableClosedRange, see the documentation comment for discussion .... In Swift 3+ What version swift is this for? – uplearned.com Feb 23 '17 at 09:36
  • 1
    I have gotten crashes if I don't protect the $1 code with an empty string check, guess it has to do with order of operations. – Raul Huerta Jun 08 '18 at 18:05
  • Be aware of exclamation marks! They can cause a crash! – goodliving Nov 19 '18 at 13:37
  • Double space between the name will produce a crash. For example ```Kunal Gupta``` – Kunal Gupta Jun 20 '19 at 05:21
  • 1
    Please see the answers using `PersonNameComponentsFormatter`. This is the right way to do it. The one by cherpak-evgeny is the best one. There is no need for reeinventing a broken wheel – laka Jan 31 '20 at 14:42
  • Works fine. If there're extra spaces, the force unwrapped will result in a crash. Need to remove extra spaces with `.compactMap { $0.count == 0 ? nil : $0 }` – MobileDev May 08 '20 at 10:02
  • You mus be caution, your function crash with this string: "John Fitzgerald Kennedy " – Tayo119 Oct 28 '20 at 15:44
12

Swift 4, little bit of extra work, but using PersonNameComponentsFormatter.

// Note: We Account for name suffix ( Example: III, II, Jr, Sr ) or prefixes ( Mr, Mrs )

let fullName = “Mr John Jacob Smith III”

let formatter = PersonNameComponentsFormatter()

guard let personNameComponents = formatter.personNameComponents(from: fullName) else {
  return ""
}

return personNameComponents.initials

// Note: Also create Extension for PersonNameComponents

// PersonNameComponents+Initials.swift

import Foundation

extension PersonNameComponents {
  var fullName: String {
    return [givenName, middleName, familyName].compactMap{ $0 }.joined(separator: " ")
  }

  var fullNameWithSuffix: String {
    return [givenName, middleName, familyName, nameSuffix].compactMap{ $0 }.joined(separator: " ")
  }

  var initials: String {
    let firstName = givenName?.first ?? Character(" ")
    let lastName = familyName?.first ?? Character(" ")
    return "\(firstName)\(lastName)".trimmingCharacters(in: .whitespaces)
  }

  // Note: If You need first, middle, last
  /*
  var initials: String {
    let firstName = givenName?.first ?? Character(" ")
    let middleName = middleName?.first ?? Character(" ")
    let lastName = familyName?.first ?? Character(" ")
    return "\(firstName)\(middleName)\(lastName)".trimmingCharacters(in: .whitespaces)
  }
  */
}
iOSCodeJunkie
  • 191
  • 1
  • 4
5

This is my solution.

Swift 5

extension String {
    var initials: String {
        return self.components(separatedBy: " ")
            .reduce("") {
                ($0.isEmpty ? "" : "\($0.first?.uppercased() ?? "")") +
                ($1.isEmpty ? "" : "\($1.first?.uppercased() ?? "")")
            }
    }
}
Tayo119
  • 331
  • 4
  • 11
1
extension String {
    var initials: String {
        return self.components(separatedBy: " ").filter { !$0.isEmpty }.reduce("") { ($0 == "" ? "" : "\($0.first!)") + "\($1.first!)" }
    }
}

This fixes the crashing problem on Guy Daher's solution.

dilaver
  • 674
  • 3
  • 17
0

Enumerate and filter by odd index

let initials = "John Fitzgerald Kennedy".componentsSeparatedByString(" ")
    .enumerate()
    .filter { (index, _) -> Bool in return index % 2 == 0 }
    .reduce("") { a, i in return a + i.element.first }

Note: This one is not care about no middle name or more than one

avdyushin
  • 1,906
  • 17
  • 21