Search code examples
iosobjective-cmsmessagemsmessagetemplatelayout

MSMessage url stays nil after setting it to url of a file


I have an app with an iOS 10 iMessage app. When I attach my file URL to an MSMessage message.URL is (null). I really don't know what's causing this. When I check the logs, I see a proper url: URL: file:///thisuser/... etc. However, message.URL logs (null).

I've build an Exporter class, this saves the file to disk and then returns the path for it.

+ (NSString *) saveToDisk:(NSDictionary *)dictionary {
    // Figure out destination name (in public docs dir)
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *zippedName = [self getExportFileName:dictionary withExtension:YES];
    NSString *zippedPath = [documentsDirectory stringByAppendingPathComponent:zippedName];

    // Export to data buffer
    NSData *gzData = [NSKeyedArchiver archivedDataWithRootObject:dictionary];

    if (gzData == nil) return FALSE;

    // Write to disk
    [gzData writeToFile:zippedPath atomically:YES];

    return zippedPath;
}

This will return something like: /Users/thisuses/Library/Developer/CoreSimulator/Devices/.../Documents/new-save.rst, where .rst is a custom file extension just for my app. This, in turn, is added to the MSMessage.

MSConversation *conversation = [self activeConversation];

MSMessageTemplateLayout *layout = [[MSMessageTemplateLayout alloc] init];
layout.image = [UIImage imageNamed:@"test"];
layout.caption = url.host;

MSMessage *message = [[MSMessage alloc] init];
message.layout = layout;

NSLog(@"Converter: %@", [Converter toDictionary:array]);
NSLog(@"Exporter: %@", [Exporter saveToDisk:[Converter toDictionary:array]]);
NSLog(@"URL: %@", [NSURL fileURLWithPath:[Exporter saveToDisk:[Converter toDictionary:array]]]);

message.URL = [NSURL fileURLWithPath:[Exporter saveToDisk:[Converter toDictionary:array]]];

NSLog(@"Message URL 1: %@", message.URL);

[conversation insertMessage:message completionHandler:^(NSError * error) {
    NSLog(@"MSConvo error: %@",error);
}];

== Edit: I added a check to the code to see if the Exporter returns a valid file path and turns out, it does.

NSURL *fileURL = [NSURL fileURLWithPath:[Exporter saveRequestToDisk:[Converter databaseToRequest:history]]];

if ([fileURL isFileURL]) {
    NSLog(@"is File URL!");
    message.URL = fileURL;
}

Solution

  • After looking into the docs and skimming this article, I think that the url property is not supposed to point to a file. Instead it should

    [...][encode] data to be transmitted with the message.

    I guess the right way to go is to

    Encode your application’s data in the URL. For example, you can encode data as key-value pairs in the URL’s query string, as shown below:

    guard let components = NSURLComponents(string: myBaseURL) else {
        fatalError("Invalid base url")
    }
    
    let size = NSURLQueryItem(name: "Size", value: "Large")
    let count = NSURLQueryItem(name: "Topping_Count", value: "2")
    let cheese = NSURLQueryItem(name: "Topping_0", value: "Cheese")
    let pepperoni = NSURLQueryItem(name: "Topping_1", value: "Pepperoni")
    components.queryItems = [size, count, cheese, pepperoni]
    
    guard let url = components.url  else {
        fatalError("Invalid URL components.")
    }
    
    message.url = url
    

    (Code taken from the docs, you might want to convert it to ObjC...)

    So instead of converting your dictionary to NSData and writing it to a file, you might want to encode it into queryItems, possibly like this:

    NSMutableArray *queryItems = [[NSMutableArray alloc] init]; // Or initWithCapacity for the sake of performance...
    NSDictionary *dict = [Converter toDictionary:array];
    
    for (id key in dict) {
        id value = queryDictionary[key];
        NSURLQueryItem *queryItem = [NSURLQueryItem queryItemWithName:key value:value];
        [queryItems addObject:queryItem];
    }
    
    [url setQueryItems:queryItems];