Search code examples
iosobjective-cios14uipasteboard

UIPasteBoard "string" property returning nil despite "hasStrings" being true


I have the following code I use to grab text that a user has copied to the clipboard from outside the app so they can paste it in within the app:

UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];

if ([pasteboard hasStrings])
{
    NSString *text = pasteboard.string;
}

This has always worked fine, until iOS14 I notice I've been getting crashes because pasteboard.string is nil despite hasStrings being true.

I looked into the documentation and discovered that indeed, it's possible for pasteboard.string to be nil:

The value stored in this property is an NSString object. The associated array of representation types is UIPasteboardTypeListString, which includes type kUTTypeUTF8PlainText. Setting this property replaces all current items in the pasteboard with the new item. If the first item has no value of the indicated type, nil is returned.

I take this mean to that some sort of string that is not kUTTypeUTF8PlainText is in the clipboard and that's why pasteboard.string is nil, but is this the correct interpretation?

I'm just confused as to what exactly is happening here and am unsure what to tell my user if I reach the case where pasteboard.string is nil?


Solution

  • -[UIPasteboard hasStrings] == YES only means the items in pasteboard have type of public.utf8-plain-text or any other types that indicates it's a string.

    But -[UIPasteboard string] can still return nil if object of class NSString cannot be constructed from any data provided by itemProviders.

    Here is an example to reproduce the situation you are in:

    First implement a test class that conforms to NSItemProviderWriting

    #import <Foundation/Foundation.h>
    
    static NSString *const UTTypeUTF8PlainText = @"public.utf8-plain-text";
    
    @interface TestObject : NSObject <NSItemProviderWriting>
    
    @end
    
    @implementation TestObject
    
    - (NSData *)randomDataWithLength:(NSUInteger)length {
        NSMutableData *data = [NSMutableData dataWithLength:length];
        SecRandomCopyBytes(kSecRandomDefault, length, data.mutableBytes);
        return data;
    }
    
    #pragma mark - NSItemProviderWriting
    
    + (NSArray<NSString *> *)writableTypeIdentifiersForItemProvider {
        return @[UTTypeUTF8PlainText];
    }
    
    - (nullable NSProgress *)loadDataWithTypeIdentifier:(nonnull NSString *)typeIdentifier forItemProviderCompletionHandler:(nonnull void (^)(NSData * _Nullable, NSError * _Nullable))completionHandler {
        // random data that an utf8 string may not be constructed from
        NSData *randomData = [self randomDataWithLength:1];
        completionHandler(randomData, nil);
        return nil;
    }
    
    @end
    
    

    Then put the test object into pastboard

    if (@available(iOS 11.0, *)) {
        UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
        TestObject *item = [TestObject new];
        [pasteboard setObjects:@[item]];
        
        if ([pasteboard hasStrings]) {
            // text may be nil
            NSString *text = pasteboard.string;
        }
    }