JOG
JOG

Reputation: 5640

Better way to get the user's name from device?

I made a function that extracts a user name from the device name.

The idea is to skip setup steps to allow the user to go straight to play upon starting the app the first time.

This is a sub-optimal approach, as I can never trust the device name to hold the name of the user. The question is: What is a better way to do this?

My function below gets the right name ...

It obviously does not get the name right if the user has changed the name of the device to something else than the default form.

- (NSString *) extractPlayerNameFromDeviceName: (NSString *) deviceName  {

    // get words in device name
    NSArray *words = [deviceName componentsSeparatedByString:@" "];
    NSMutableArray *substrings = [[NSMutableArray alloc] init]; 
    for (NSString *word in words) {
        NSArray *subwords = [word componentsSeparatedByString:@"'"];
        [substrings addObjectsFromArray:subwords];
    }

    // find the name part of the device name
    NSString *playerName = [NSString stringWithString: @""];
    for (NSString *word in substrings) {
        if ([word compare:@"iPhone"] != 0
            && [word compare:@"iPod"] != 0
            && [word compare:@"iPad"] != 0
            && [word length] > 2) {
            playerName = word;
        }
    }

    // remove genitive
    unichar lastChar = [playerName characterAtIndex:[playerName length] - 1];
    if (lastChar == 's') {
        playerName = [playerName substringToIndex:[playerName length] - 1];
    }
    lastChar = [playerName characterAtIndex:[playerName length] - 1];
    if (lastChar == '\'') {
        playerName = [playerName substringToIndex:[playerName length] - 1];
    }
    return playerName;
}

I use it for suggesting a username in my app. This way, most users won't have to bother writing their usernames.

My app is not connected to any other service like iTunes or Facebook, but every user needs a user name. So how do I get the name?

Upvotes: 14

Views: 11065

Answers (7)

Timo
Timo

Reputation: 187

Since iOS 16 you can't access the user-assigned name of the phone, you can just get the generic name of the device. This is because of new privacy features from Apple. However you can request an entitlement to get that information if it is necessary for your app.

The criteria are:

  • The user-assigned device name is visible to the user in your app’s UI. You must provide screenshots of this UI to request the entitlement.
  • The user-assigned device name is powering a multi-device feature. Your app uses the user-assigned device name solely for functionality that’s visible to the user so that they can identify their own device, and the functionality involves interaction between multiple devices that the same user operates. For example, an app that has multi-device syncing functionality might show the user-assigned device name for each device so that the user can select between them. You must provide screenshots of this UI to request the entitlement.
  • The feature using the user-assigned device name is available to all, or the vast majority of, users. The feature provides an important component of your app’s functionality to a majority of your app’s users.
  • Your app doesn’t use the user-assigned device name for tracking or fingerprinting. You’re responsible for all code in your app, including any integrated SDKs. For more information about tracking, see Tracking, and for fingerprinting, see User Privacy and Data Use.
  • Your app doesn’t share the user-assigned device name with any service providers or third parties other than cloud-hosting service providers. Prohibited third parties include, but aren’t limited to, third-party SDKs, ad networks, and mobile measurement partners (MMPs). There’s an exception for cloud-hosting service providers for the purposes of storage or syncing only.

See full documentation:

Hope this will help someone who is trying to find a way to get the name :)

Upvotes: 0

Beginner
Beginner

Reputation: 515

Swift 5 + Localization

Here's an update as I too wanted to personalize an onboarding and simplify the contact import process in an app meant for people in cognitive decline. More details / deficits / revisions over time are kept in a Gist.

  • Prior answers removed two letter words (e.g., "de") under the assumption those were not part of a name. That's not true in Italian or for, say, Chris Ng or Tim Yu. Instead, feeding a cleaner substring into PersonNameComponentsFormatter can pull out a given, family, or full name over more languages, including those where "de" or "la" matter.
  • I've added a separate track for Chinese.
  • A better build out could cover more edge cases for more languages. The approach below is less annoying to update since it uses Swift 5 string parsing instead of Regex layers.
class Autofiller {
    
    enum NameComponent {
        case givenName
        case familyName
        case fullNameInCurrentPersonNameComponentsFormatterStyle
    }
    
    /// Proposes a localized name based on UIDevice.current.name (under the assumption that it contains a name).
    /// - Returns: A user's probable first, last, or full name — or a default if detection fails.
    ///
    /// Be aware that:
    /// * Non-name words may slip through
    /// ```
    /// Paul The Great // Paul the Great
    /// Paul's Really Old iPhone // Paul
    /// ```
    /// * This is only tested for romance languages and Chinese.
    /// * Chinese names return full name in `givenName` only mode. Options require uncommenting internal code.
    ///
    /// - Parameter name: Choose between given, family, and full name
    /// - Parameter style: Options for [PersonNameComponentsFormatter](https://developer.apple.com/documentation/foundation/personnamecomponentsformatter)
    /// - Parameter defaultUponFailure: Specify your default string should guessing fail
    func guessNameOfDeviceOwner(name: NameComponent,
                                style: PersonNameComponentsFormatter.Style = .default,
                                placeholderUponFailure: String = "Good Looking") -> String {
        
        let deviceName = UIDevice.current.name
        let nameFormatter = PersonNameComponentsFormatter()
        nameFormatter.style = style
        
        if let chineseName = extractNameComponentsInChinese(from: deviceName) {
            switch name {
            case .givenName:
                return nameFormatter.string(from: chineseName)
            // DEFAULT: RETURN FULL NAME (EVEN WHEN OTHER LANGUAGES RETURN GIVEN ONLY)
            // OPTION: CUTESY INFORMAL GIVEN NAME
            // if let givenName = chineseName.givenName {
            // return String("小").appending(givenName)
            case .familyName:
                if let familyName = chineseName.familyName {
                    return familyName
                }
            // OPTION: RESPECTFUL FAMILY NAME
            // if let familyName = chineseName.familyName {
            // return String("老").appending(familyName)
            case .fullNameInCurrentPersonNameComponentsFormatterStyle:
                return nameFormatter.string(from: chineseName)
            }
        }
        
        if let latinName = extractNameComponentsByPrefixOrSuffix(from: deviceName) {
            switch name {
            case .givenName:
                if let givenName = latinName.givenName {
                    return givenName
                }
            case .familyName:
                if let familyName = latinName.familyName {
                    return familyName
                }
            case .fullNameInCurrentPersonNameComponentsFormatterStyle:
                return nameFormatter.string(from: latinName)
            }
        }
        
        return placeholderUponFailure
    }
    
    /// Process common styles for English (Ryan's iPhone), Swedish (Ryan iPhone), French (iPhone de Ryan)
    private func extractNameComponentsByPrefixOrSuffix(from input: String) -> PersonNameComponents? {
        let formatter = PersonNameComponentsFormatter()
        
        let prefixes = ["iPhone de ",
                        "iPad de ",
                        "iPod de "
        ]
        
        for prefix in prefixes {
            guard input.contains(prefix) else { continue }
            var inputComponents = input.components(separatedBy: prefix)
            // First element is either empty or assumed to be extraneous
            inputComponents.removeFirst()
            let possibleName = inputComponents.joined()
            // Note: .personNameComponents(from:) will ignore brackets, parentheses
            guard let nameComponents = formatter.personNameComponents(from: possibleName) else { return nil }
            return nameComponents
        }
        
        let suffixes = ["'s iPhone",
                        "'s iPad'",
                        "'s iPod",
                        "'s ", // Capture if user removed "i" or has a descriptor (e.g., Paul's Really Old iPhone)
                        "iPhone", // For Swedish style, reached if posessive language not present
                        "iPad",
                        "iPod",
                        "Phone", // Latter iterations, if reached, cover an edge case like me, a nerd who named his phone "RyPhone"
                        "Pad",
                        "Pod"
        ]
        
        for suffix in suffixes {
            guard input.contains(suffix) else { continue }
            var inputComponents = input.components(separatedBy: suffix)
            
            // The last component is either emptty, contains the model (e.g., "XS"), or duplicate device number (e.g., "(2)")
            inputComponents.removeLast()
            let possibleName = inputComponents.joined()
            guard let nameComponents = formatter.personNameComponents(from: possibleName) else { return nil }
            return nameComponents
        }
        
        // If no prefix/suffix matches, attempt to parse a name. Otherwise return nil to indicate failure.
        guard let possibleName = formatter.personNameComponents(from: input) else { return nil }
        return possibleName
    }
    
    /// Process for Chinese name apart from neighboring English (e.g., "某人的iPhone")
    private func extractNameComponentsInChinese(from input: String) -> PersonNameComponents? {
        guard let range = input.range(of: "\\p{Han}*\\p{Han}", options: .regularExpression) else { return nil }
        // Extract of only Chinese characters, ignoring "iPhone" etc
        var possibleName = input[range]
        // Remove possible instance of "cell phone"
        possibleName = Substring(String(possibleName).replacingOccurrences(of: "手机", with: ""))
        // Remove possible posessive referring to iPhone or cell phone
        if possibleName.last == "的" { possibleName.removeLast(1) }
        let formatter = PersonNameComponentsFormatter()
        guard let nameComponents = formatter.personNameComponents(from: String(possibleName)) else { return nil }
        return nameComponents
    }
}

Upvotes: 4

iGranDav
iGranDav

Reputation: 2460

I've converted original Owen Godfrey answer to Swift and updated the Regexpr to support more patterns like User's iPhone 6S or iPhone 5 de User ...

I've created a Gist here: https://gist.github.com/iGranDav/8a507eb9314391338507

extension UIDevice {

func username() -> String {

    let deviceName = self.name
    let expression = "^(?:iPhone|phone|iPad|iPod)\\s+(?:de\\s+)?(?:[1-9]?S?\\s+)?|(\\S+?)(?:['']?s)?(?:\\s+(?:iPhone|phone|iPad|iPod)\\s+(?:[1-9]?S?\\s+)?)?$|(\\S+?)(?:['']?的)?(?:\\s*(?:iPhone|phone|iPad|iPod))?$|(\\S+)\\s+"

    var username = deviceName

    do {
        let regex = try NSRegularExpression(pattern: expression, options: .CaseInsensitive)
        let matches = regex.matchesInString(deviceName as String,
                                            options: NSMatchingOptions.init(rawValue: 0),
                                            range: NSMakeRange(0, deviceName.characters.count))
        let rangeNotFound = NSMakeRange(NSNotFound, 0)

        var nameParts = [String]()
        for result in matches {
            for i in 1..<result.numberOfRanges {
                if !NSEqualRanges(result.rangeAtIndex(i), rangeNotFound) {
                    nameParts.append((deviceName as NSString).substringWithRange(result.rangeAtIndex(i)).capitalizedString)
                }
            }
        }

        if nameParts.count > 0 {
            username = nameParts.joinWithSeparator(" ")
        }
    }
    catch { NSLog("[Error] While searching for username from device name") }

    return username
}
}

Upvotes: 3

Owen Godfrey
Owen Godfrey

Reputation: 3373

I'd like to offer an improvement on Ricky Helegesson's answer. It has the following features;

  • It is a little smaller, although less efficient because it uses regular expressions, but then I suppose it should be called only once.
  • I have expended it to include "phone" as well as "iPod, "iPhone" and "iPad".
  • It only removes "'s" when it immediately preceded by "iPad", "iPhone" etc., but only at the end of the string.
  • It removes "iPad" and so on when they are the first word, as in "iPad Simulator".
  • It capitalises the first letter of each word.
  • It is case insensitive.
  • It is a function because it has no dependencies.

Here is the code:

NSArray * nameFromDeviceName(NSString * deviceName)
{
    NSError * error;
    static NSString * expression = (@"^(?:iPhone|phone|iPad|iPod)\\s+(?:de\\s+)?|"
                                    "(\\S+?)(?:['’]?s)?(?:\\s+(?:iPhone|phone|iPad|iPod))?$|"
                                    "(\\S+?)(?:['’]?的)?(?:\\s*(?:iPhone|phone|iPad|iPod))?$|"
                                    "(\\S+)\\s+");
    static NSRange RangeNotFound = (NSRange){.location=NSNotFound, .length=0};
    NSRegularExpression * regex = [NSRegularExpression regularExpressionWithPattern:expression
                                                                            options:(NSRegularExpressionCaseInsensitive)
                                                                              error:&error];
    NSMutableArray * name = [NSMutableArray new];
    for (NSTextCheckingResult * result in [regex matchesInString:deviceName
                                                         options:0
                                                           range:NSMakeRange(0, deviceName.length)]) {
        for (int i = 1; i < result.numberOfRanges; i++) {
            if (! NSEqualRanges([result rangeAtIndex:i], RangeNotFound)) {
                [name addObject:[deviceName substringWithRange:[result rangeAtIndex:i]].capitalizedString];
            }
        }
    }
    return name;
}

To use this for return a name;

NSString* name = [nameFromDeviceName(UIDevice.currentDevice.name) componentsJoinedByString:@" "];

This is somewhat complex, so I'll explain;

  1. The regular expression holds three parts;
    1. At the start of the string, match but do not return "iPhone", "iPod", "iPad" or "phone" and an optional word "de".
    2. At the end of the string, match and return a word that is followed by and optional " 's" (which is not returned) and then "iPad", "iPhone", "iPod" or "phone" (which are not returned either).
    3. This match is the same as the previous, but it should work for Chinese device names. (Adapted from Travis Worm's submission. Please tell me if its wrong.)
    4. Match and return any word that doesn't match the previous rules.
  2. Iterate through all the matches, capitalise them and add them to the array.
  3. Return the array.

If a name ends in "s" without an apostrophe before "iPad" etc., I don't try to change it because there is not foolproof way of figuring out if the "s" is a part of the name or a pluralisation of the name.

Enjoy!

Upvotes: 8

Travis Worm
Travis Worm

Reputation: 166

NSString *dname=[[UIDevice currentDevice] name];
dname=[dname componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"'的"]][0];

Upvotes: 0

Ricky Helgesson
Ricky Helgesson

Reputation: 3586

Here is an alternative, that gets all names. Also, it does not remove 's' at the end of languages that uses "de" or "'s". Also, it capitalizes the first letter of each name.

Method implementation:

- (NSArray*) newNamesFromDeviceName: (NSString *) deviceName
{
    NSCharacterSet* characterSet = [NSCharacterSet characterSetWithCharactersInString:@" '’\\"];
    NSArray* words = [deviceName componentsSeparatedByCharactersInSet:characterSet];
    NSMutableArray* names = [[NSMutableArray alloc] init];

    bool foundShortWord = false;
    for (NSString *word in words)
    {
        if ([word length] <= 2)
            foundShortWord = true;
        if ([word compare:@"iPhone"] != 0 && [word compare:@"iPod"] != 0 && [word compare:@"iPad"] != 0 && [word length] > 2)
        {
            word = [word stringByReplacingCharactersInRange:NSMakeRange(0,1) withString:[[word substringToIndex:1] uppercaseString]];
            [names addObject:word];
        }
    }
    if (!foundShortWord && [names count] > 1)
    {
        int lastNameIndex = [names count] - 1;
        NSString* name = [names objectAtIndex:lastNameIndex];
        unichar lastChar = [name characterAtIndex:[name length] - 1];
        if (lastChar == 's')
        {
            [names replaceObjectAtIndex:lastNameIndex withObject:[name substringToIndex:[name length] - 1]];
        }
    }
    return names;
}

Usage:

// Add default values for first name and last name
NSString* deviceName = [[UIDevice currentDevice] name];
NSArray* names = [self newNamesFromDeviceName:deviceName];
// This example sets the first and second names as the text property for some text boxes.
[self.txtFirstName setText:[names objectAtIndex:0]];
[self.txtLastName setText:[names objectAtIndex:1]];
[names release];

Upvotes: 4

Henri Normak
Henri Normak

Reputation: 4725

If it's just meant for iPods and iPhones, then why even use a username? If you need to identify the device for your web-service, there are other unique values each device has (such as UDID). Other option would be to let the user pick a contact from the address book that represents themselves and use that data.

Upvotes: 1

Related Questions