Search code examples
iosobjective-cstringwithformat

Multiple arguments in stringWithFormat: “n$” positional specifiers


In our current implementation we want to change our string arguments (Push notification loc-args) and add new arguments. But we want user's of our old Versions to still use argument #3 and for new user's we want to user argument #4. So in our new implementation we have following code :

NSString *format = @"%2$@,  %1$@  ,%4$@";
NSArray *arg = @[@"Argument 1", @"Argument 2",@"Argument 3",@"Argument 4"];
NSString *ouptput = [NSString stringWithFormat:format, arg[0], arg[1], arg[2], arg[3]];

OutPut: Argument 2, Argument 1 ,Argument 3

We are expecting it to be

Argument 2, Argument 1 ,Argument 4

How can we achieve Argument 4 in place. Any other alternative of stringWithFormat:

Note: Apple lock screen push notification is correct (Argument 2, Argument 1 ,Argument 4) but stringWithFormat: not handles it that way


Solution

  • I implemented a custom method to achieve expected output. This method can handle missing positional specifier. This method will only work for format containing positional specifier %n$@.

    /**
     @param format String format with positional specifier
     @param arg Array of arguments to replace positional specifier in format
     @return Formatted output string
     */
    +(NSString*)stringWithPositionalSpecifierFormat:(NSString*)format arguments:(NSArray*)arg
    {
        static NSString *pattern = @"%\\d\\$@";
    
        NSError *error;
        NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:&error];
    
        NSMutableString *mString = [[NSMutableString alloc] initWithString:format];
        NSArray *allMatches = [regex matchesInString:format options:0 range:NSMakeRange(0, [format length])];
        if (!error && allMatches>0)
        {
            for (NSTextCheckingResult *aMatch in allMatches)
            {
                NSRange matchRange = [aMatch range];
                NSString *argPlaceholder = [format substringWithRange:matchRange];
                NSMutableString *position = [argPlaceholder mutableCopy];
                [position replaceOccurrencesOfString:@"%" withString:@"" options:NSCaseInsensitiveSearch range:NSMakeRange(0, [position length])];
                [position replaceOccurrencesOfString:@"$@" withString:@"" options:NSCaseInsensitiveSearch range:NSMakeRange(0, [position length])];
                int index = position.intValue;
                //Replace with argument
                [mString replaceOccurrencesOfString:argPlaceholder withString:arg[index-1] options:NSCaseInsensitiveSearch range:NSMakeRange(0, [mString length])];
            }
        }
        return mString;
    }