Resolved Has anybody tried to use the new styled text support in iOS 6? What a mess

Discussion in 'iOS Programming' started by Duncan C, May 8, 2013.

  1. Duncan C, May 8, 2013
    Last edited: May 9, 2013

    Duncan C macrumors 6502a

    Duncan C

    Joined:
    Jan 21, 2008
    Location:
    Northern Virginia
    #1
    The text view classes UITextView, UITextField and UILabel (and maybe a few others) have been extended to handle attributed strings. Great, right?

    Well, not really.

    First, if you want to support iOS 5 also, you're out of luck.

    Next, the iOS version of NSAttributedString doesn't have any way to create an attributed string from an RTF file.

    The Mac OS version of NSAttributedString has a handy method, initWithRTF:documentAttributes: that lets you create an attributed string from an RTF file. So you an include an RTF file in your project, edit in Xcode (who's editor knows how to edit styled rtf documents, cool!) and then write a couple of lines of code that load the contents of that rtf file into an NSAttributedString and display it.

    Not so on iOS. There is no easy way to create NSAttributedString objects.

    Ok, so I looked into it, and NSAttributedString supports NSCoding.

    I wrote a little Mac tool that takes an RTF file and saves it out as a new file type, .data, which is just an NSArchiver output from sending the keyed archive class method archiveRootObject:toFile: an attributed string. Ok, so far so good.

    Now I want to display a plain text version of this string when running on iOS 5. iOS 5 supports attributed strings, but not text views that know how to display them.

    So at runtime I write code that loads my .data file into an attributed string. I then check to see if my UITextField responds to the setAttributedText method. If it does, great. I set the attributedText property, and there you go. Under iOS 5, I simply use the NSAttributedString's string method to get a plain text string, and assign that to the text property of my text view. Simple, right?

    Wrong. It turns out that the iOS 5 version of NSAttributedString doesn't know how to handle paragraph styles and a few other things that you take for granted when composing even a simple RTF document.

    Grr. So now I have to go back to my Mac RTF to data conversion utility and make it export both a data archive of the object and a plain text version. My iOS app needs to decide at runtime which OS it's running on, and load the plain text under iOS 5 and the styled text .data file for iOS 6.

    Now I have 3 versions of my styled text to deal with: the original RTF file, the .data iOS readable version, and the plain text version for iOS 5.

    We're planning on localizing the app we are shipping for several languages, and so we will have a number of styled text files to deal with, and probably multiple revisions of those files. Sounds like a configuration management nightmare in the making.

    I spent all day today devising a solution to this problem.

    Here's what I did.

    I turned my Mac utility that converts RTF files into data files+text files into a command line tool. It takes a source filename as a required parameter, and an optional output directory as a second parameter. (There's also an overwrite switch in case the output files already exist) If no output directory is specified, it tries to write the .data and .txt output files in the same directory as the source .rtf file.

    I then spent the day figuring out how to integrate this tool into the Xcode build process for our app. It was NOT easy.

    I got some help from stack overflow, and did a lot of fiddling.

    The good news is that I now have full integration of rtf files with my project. If I add .rtf files to my project, the build process automatically generates the required .data and .txt files that I need to display that text under both iOS 5 and iOS 6. Since it's a build rule, it understands dependencies, and the conversion is only run if the source file changes. If I edit the RTF file in Xcode or TextEdit, the next time I run the app, the new content is automatically generated and installed in the app.

    The bad news is what I had to do to get Xcode to run my command line tool as part of the build. The only way I could get it to work was to copy the command line binary inside the Xcode project bundle. (in /contents/Developer/usr/bin. Installing the tool in either the user level or system level bin directory doesn't work. WTF?

    This solution works, but it's fragile. Any time I update Xcode, it's going to stop working and I will need to copy the file back into the new version of Xcode. Ugh. Ugh ugh ugh.

    Does anybody out there know of a way that I can install the command line tool (not a script file, mind you, but a compiled binary tool) into the PROJECT directory, or as a second choice, as system directory? Modifying Xcode itself is a rather nasty, hackish solution.
     
  2. chown33 macrumors 604

    Joined:
    Aug 9, 2009
    #2
    I can understand why Xcode might not look for commands in a user-level bin directory (~/bin may be a security risk). But not looking in /bin or /usr/bin seems excessive. If you can find out exactly what $PATH Xcode is really using, there may be a way to trick it. Or if Xcode isn't using PATH, exactly where does it look for commands?


    If there's an 'env' command, or if Xcode uses a shell (e.g. '/bin/bash -c'), then there's this well-known Unix trick:
    Code:
    PATH=/your/cmdDir/here:/bin:/usr/bin:/other/xcode/dirs env yourCmdHere args
    
    The PATH=xxx part is the shell syntax that means "set the env-var, then interpret the remainder of the command-line". The 'env' command then appears, and is located in /usr/bin (found via PATH). The 'env' command then uses PATH to find yourCmdHere, passing args.

    If a shell isn't used, then the leading PATH=xxx won't work, so then you'd use something like this:
    Code:
    /usr/bin/env PATH=pathStuffHere yourCmdHere args
    
    Or maybe an absolute pathname for the cmd works. What does this do:
    Code:
    /usr/local/bin/yourCmdHere args
    
    Assuming yourCmdHere is located in /usr/local/bin.
     
  3. Duncan C thread starter macrumors 6502a

    Duncan C

    Joined:
    Jan 21, 2008
    Location:
    Northern Virginia
    #3
    Woo hoo! Problem solved!

    Thanks for your input. Your suggestion of explicitly naming the path to my tool was what it took to make me figure this out.

    My tool is called RTFToData.

    Now the script for my build rule uses the SOURCE_ROOT environment variable to point to my command line tool. I just dragged the tool into the top level of the project directory and added it to the project (with the add to target checkbox turned off, obviously)

    My new script line looks like this:

    "${SOURCE_ROOT}"/RTFToData -o "${INPUT_FILE_PATH}" "${DERIVED_FILES_DIR}"

    It invokes the tool right in the top level directory for the project. That's the ideal solution for us. That way I don't have to install it anywhere on a developer's system. Simply checking out the project from source control installs the tool in the right location.

    Thanks again for your time and your suggestions. Your input was the key to solving the problem.

     
  4. chown33 macrumors 604

    Joined:
    Aug 9, 2009
    #4
    So absolute paths still work. Honestly, I'd be surprised (and annoyed) if they didn't, but who can predict the wily ways of Xcode.

    Glad it worked out.

    Resolved?
     

Share This Page