Skip to main content

How to Use OUIEditableFrame with the OmniGroup Framework

TLDR: This blog post provides a detailed tutorial on how to implement OmniGroup’s OUIEditableFrame in an iOS project to render text with proper kerning, which cannot be achieved with standard UIKit controls due to a bug. The guide walks through creating and configuring an Xcode workspace, cloning the OmniGroup project from GitHub, adding necessary frameworks and dependencies, and writing the required view controller code.

Learn to implement OmniGroup's OUIEditableFrame for proper text kerning in iOS projects with this step-by-step Xcode tutorial.

Table of Contents:

  1. Introduction
  2. Create and Configure Xcode Workspace
  3. Clone the OmniGroup Project from GitHub
  4. Add the FixStringsFile Project
  5. Add OmniBase
  6. Add the Rest of the Frameworks
  7. Write View Controller Code
  8. Making It Work in iOS 5
  9. Get Rid of the Static Analyzer Message

If you need a UITextField that renders text with proper kerning you don’t have many of options. Writing all the CoreText code is a major undertaking and not for the average iOS programmer. One third-party option is EGOTextView but it hasn’t been updated for a couple of years and should be considered abandoned. While iOS started supporting attributed strings for UIKit controls a bug in UITextView prevented it from kerning properly.

The only other real option is OmniGroup’s OUIEditableFrame which is part of their iOS/macOS X open source frameworks. Omni uses these frameworks in their own highly respected products. However, it’s not exactly easy to incorporate into your own project. There are several places where dependencies need to be specified and the Xcode workspace needs to be set up a particular way.

I haven’t seen any documentation on how to use the OmniGroup framework which is why I created this tutorial. This will serve as a reference to myself in the future and hopefully others will find it useful as well. The goal of this tutorial is to create a project which uses OUIEditable frame in the simplest possible manner.

DISCLAIMER: There is no warranty that the approach to a possible solution is the best one.

Create and Configure Xcode Workspace

Create a new Xcode project using the Single View Application template for iOS.

Target iPad. Use Storyboards and Automatic Reference Counting. Name the project SimpleEditableFrame.

Close the project with File | Close Project but leave Xcode running. Create a workspace with File | New Workspace and call it SimpleEditableFrame. Save it in the SimpleEditableFrame directory which Xcode created in the previous step.

Add SimpleEditableFrame.xcodeproj to the workspace. I drag the .xcodeproj file from Finder into Xcode’s navigation (left-most) pane. If you usually use a different method to add projects to a workspace feel free to use that.

Clone the OmniGroup project from GitHub

We don’t want to mix the OmniGroup git clone with the git repository for our own project so we’re going to clone it into a directory that’s completely separate from our project. On my system I keep the OmniGroup files in ~/dev/Omni/. Create this directory on your system and clone the git repository.

cd ~/dev/Omni/
git clone git://github.com/omnigroup/OmniGroup  
cd OmniGroup  
git submodule update --init

When the git clone is completed you can copy the OmniGroup directory to your project directory (omiting the .git directory). The project directory should now contain the following

  • OmniGroup/
  • SimpleEditableFrame/
  • SimpleEditableFrame.xcodeproj
  • SimpleEditableFrame.xcworkspace

Add the FixStringsFile Project

Add OmniGroup/Tools/FixStringsFile/FixStringsFile.xcodeproj to the workspace. Make sure it’s at the same level as SimpleEditableFrame and not contained within it. The workspace should now contain two projects at the same level: SimpleEditableFrame and FixStringsFile.

In the FixStringsFile project open Configurations/Target-Mac-Common.xcconfig. Comment out the following line:

OMNI_MAC_CODE_SIGN_IDENTITY = Mac Developer:

If you are developing only for iOS this line is unnecessary and will likely cause problems when building.

Edit the Scheme (⌘<) and choose the Build action. Add the FixStringsFile target using the + button at the bottom of the list. Drag FixStringsFile to the top of the list. Uncheck Parallelize Build and Find Implicit Dependencies.

Build (⌘B). The project should build cleanly at this point.

Add OmniBase

I find it helpful to add just OmniBase at first and get the project to build with only that framework. Then I add the rest of the Omni frameworks. This requires a bit of extra work but if you’re just getting started it’s a good idea to minimize the amount you’re trying to change at any one time. If something goes wrong there are fewer things that could be the cause of the problem and the changes required to make fixes are easier since you’re only dealing with one framework instead of several.

Add the OmniGroup/Configurations directory to the SimpleEditableFrame project by right-clicking the project and choosing Add Files to “SimpleEditableFrame”… from the context menu. Make sure Copy groups into destination group’s folder (if needed) is unchecked. The files already exist in the project directory. We just need to create references to them in the project.

In Configurations/Configurations/Target-Mac-Common.xcconfig the OMNI_MAC_CODE_SIGN_IDENTITY line is probably already commented out. Double check to ensure it is.

Right-click on the project and choose New Group. Name the new group OmniFrameworks. Add OmniGroup/Frameworks/OmniBase/OmniBase.xcodeproj to the OmniFrameworks group. I use drag-n-drop from Finder. You can use what works best for you.

Select the SimpleEditableFrame project in the navigator and go to Build Phases for the SimpleEditableFrame target. In the Link Binary with Libraries section add libOmniBase.a. In the Target Dependencies section add OmniBaseTouch.

Build (⌘B). The project should build cleanly at this point. This is a major milestone in the process.

Add the Rest of the Frameworks

Add these system frameworks in the Link Binary with Libraries section of the Build Phases tab.

  • CoreText.framework
  • QuartzCore.framework
  • MobileCoreServices.framework
  • MessageUI.framework
  • Security.framework
  • SystemConfiguration.framework
  • AssetsLibrary.framework
  • ImageIO.framework
  • libz.dylib
  • libxml2.dylib

In the navigator drag these into the Frameworks group to keep things organized.

Add the remaining OmniGroup frameworks to the workspace. Again, I use drag-n-drop from Finder to Xcode but you can use what works best for you. Just be careful to ensure they land in the OmniFrameworks group.

  • OmniFoundation.xcodeproj
  • OmniQuartz.xcodeproj
  • OmniAppKit.xcodeproj
  • OmniFileStore.xcodeproj
  • OmniFileExchange.xcodeproj
  • OmniUI.xcodeproj
  • OmniUIDocument.xcodeproj
  • OmniUnzip.xcodeproj

Add the following to Link Binary with Libraries in the Build Phases tab.

  • libOmniFoundation.a
  • libOmniQuartz.a
  • libOmniAppKit.a
  • libOmniFileStore.a
  • libOmniFileExchange.a
  • libOmniUnzip.a
  • libOmniUI.a
  • libOmniUIDocument.a

Add the following to Target Dependencies in the Build Phases tab.

  • OmniFoundationTouch (OmniFoundation)
  • OmniFoundationTouchTests (OmniFoundation)
  • OmniQuartzTouch (OmniQuartz)
  • OmniAppKitTouch (OmniAppKit)
  • OmniAppKitTouchTests (OmniAppKit)
  • OmniUnzipTouch (OmniUnzip)
  • OmniFileStoreTouch (OmniFileStore)
  • OmniUITouch (OmniUI)
  • OmniUIDocument (OmniUIDocument)

Edit the Scheme (⌘<) and choose the Build action. Add the following libraries as targets. I’m not sure if the order is important but just to be safe I kept these in the same order as shown in Omni’s example TextEditor application.

  • OmniBaseTouch
  • OmniFoundationTouch
  • OmniUnzipTouch
  • OmniFileStoreTouch
  • OmniAppKitTouch
  • OmniQuartzTouch
  • OmniFileExchangeTouch
  • OmniUITouch

Go back to the Build Phases screen. Select Add Build Phase and then Add Run Script.

Paste the following line as the contents of the script.

OmniGroup/Scripts/CopyLibraryResources

Build (⌘B). The project should build with one warning from the static analyzer. This is okay. If you want to get rid of the warning see the section at the end of this tutorial. You are now building the project with all of the required Omni frameworks classes. This is another major milestone.

Write View Controller Code

In the SimpleEditableFrame project open ViewController.m.

Import the OUIEditableFrame header.

#import <OmniUI/OUIEditableFrame.h>

Create some defines for the font information we’ll use in the app. Add these after the imports.

#define FONT_NAME @"HelveticaNeue-Light"
#define FONT_WEIGHT 36.0
#define TEST_STRING @"AVAVA"

Add the following to viewDidLoad so it looks like this.

- (void)viewDidLoad
{
    [super viewDidLoad];

    // Create attributed string for use in the OUIEditableFrame
    CTFontRef fontFace = CTFontCreateWithName((__bridge CFStringRef)(FONT_NAME), FONT_WEIGHT, NULL);
    NSMutableDictionary *attributes = [[NSMutableDictionary alloc] init];
    [attributes setObject:(__bridge id)fontFace forKey:(NSString*)kCTFontAttributeName];
    [attributes setObject:[UIColor blackColor] forKey:(NSString*)kCTForegroundColorAttributeName];
    NSAttributedString *attrStr = [[NSAttributedString alloc] initWithString:TEST_STRING];

    // Create the OUIEditableFrame and add it to the view
    OUIEditableFrame *omniEditableFrame = [[OUIEditableFrame alloc] initWithFrame:CGRectMake(60, 100, 648, 200)];
    omniEditableFrame.defaultCTFont = fontFace;
    omniEditableFrame.backgroundColor = [UIColor lightGrayColor];
    omniEditableFrame.textColor = [UIColor blackColor];
    omniEditableFrame.attributedText = attrStr;
    [self.view addSubview:omniEditableFrame];

    // For comparison create a regular UITextField and add it to the view
    UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(60, 340, 648, 200)];
    textField.backgroundColor = [UIColor lightGrayColor];
    textField.font = [UIFont fontWithName:FONT_NAME size:FONT_WEIGHT];
    textField.text = TEST_STRING;
    [self.view addSubview:textField];
}

Build and Run (⌘R). You should notice the the OUIEditableFrame has less space between the characters. It’s rendering with CoreText instead of WebKit which the UITextField uses under the hood. CoreText honors the font’s built-in kerning.

Of course, all this will likely be unecessary in iOS 7 but for projects which need to support older versions of iOS this is practically the only way to get CoreText font rendering in an editable text field.

Making It Work in iOS 5

If you try to run this in the iOS 5 simulator and get errors like the following you’ll need to do a little extra configuring in Xcode.

dyld: Symbol not found: _objc_setProperty_nonatomic

This is usually the case when the deployment targets for dependent libraries are higher than the deployment target for the main project. In this case I set the project’s iOS Deployment Target to 5.1. I also had to manually set the iOS Deployment Target for the Omni libraries to 5.1 as well. First select each of the Omni projects in the navigator and set the iOS Deployment Target to 5.1 in the Project Info screen.

You’ll need to do this for each of the included frameworks. Next, select the library targets for each of the included Omni frameworks and set the iOS Deployment Target to 5.1 in the Build Settings screen.

Do this for each library target in each of the included Omni frameworks. Build & Run (⌘R) and it should run in the iOS 5 simulator.

Get Rid of the Static Analyzer Message

Chances are good you see the following message when building.

Instance variable used while 'self' is not set to the result of '[(super or self) init...]

If you examine the code in question you can see that [super init] is indeed being called and the code is entirely safe. This is a case of Xcode’s static analyzer getting tripped up and reporting a false positive. The easiest way to turn off this warning is to wrap the problem code in a __clang_analyzer__ directive. For example:

#ifndef __clang_analyzer__

// Code the analyzer should ignore

#endif

Because the problem code in this case spans a couple of methods we’ll need to be careful about where to place the directives. It should cover both the mutableCopyWithZone and initWithParagraphStyle methods. Here’s what this section of the file looks like for me.

#ifndef __clang_analyzer__

- (id)mutableCopyWithZone:(NSZone *)zone;
{
    return [[OAMutableParagraphStyle alloc] initWithParagraphStyle:self];
}

@end

@implementation OAMutableParagraphStyle

+ (OAParagraphStyle *)defaultParagraphStyle;
{
    // Return a mutable instance when sent to the mutable subclass
    return [[[super defaultParagraphStyle] mutableCopy] autorelease];
}

- initWithParagraphStyle:(OAParagraphStyle *)original;
{
    if (!(self = [super init]))
        return nil;

    if (original) {
        memcpy(&_scalar, &original->_scalar, sizeof(_scalar));
        _tabStops = [[NSArray alloc] initWithArray:original->_tabStops];
    }

    return self;
}

#endif

This is a completely optional step but if you get tired of seeing that little blue icon on each build this is how to safefly supress it.

You can also check out the source code on GitHub.

Comments

Popular posts from this blog

What is .csp extension? C++ Server Pages

C++ Server Pages C++ Server Pages (CSP) is a Web Engine for advanced Web Application Development, that uses blended Markup Language / C++ scripts ( such as HTML/C++, XML/C++, WML/C++ etc.) Similar to ASP and JSP, it provides a great easiness in creating web pages with dynamic content, as well as complex business applications. However, instead of Java, Javascript or VBscript, it uses C++ . This brings some significant advantages: Incredibly high processing efficiency. Benchmarks have shown a range of 80 to 250 times higher processing speed than ASP. The use of pure C++ allows the use of tons of libraries that are currently available. It is important to notice that the libraries written in C++ are tens or hundreds of times more than in any other language. It is widely accepted that the most skilled programmers in the IT market are the C++ ones. However, CGI, ISAPI and other frameworks where C++ applies, do not provide the web developer with facilities for efficient app

Valid styles for converting datetime to string

I wrote this little table and procedure to help me remember what style 104 did, or how to get HH:MM AM/PM out of a DATETIME column. Basically, it populates a table with the valid style numbers, then loops through those, and produces the result (and the syntax for producing that result) for each style, given the current date and time. It uses also a cursor. This is designed to be a helper function, not something you would use as part of a production environment, so I don't think the performance implications should be a big concern. Read more »

The Python Code Handbook (free book with 40 code examples)

Introducing the Python Code Handbook from FreeCodeCamp that are worth your time: 1. This handbook will teach you Python for beginners through a series of helpful code examples. You'll learn basic data structures, loops, and if-then logic. It also includes plenty of project-oriented learning resources you can use to dive even deeper. (full handbook):  https://www.freecodecamp.org/news/python-code-examples-simple-python-program-example/ 2. freeCodeCamp just published this course to help you pass the Google Associate Cloud Engineer certification exam. If you want to work as a DevOps or a SysAdmin, this cert may be worth your time. You'll learn Cloud Engineering fundamentals, Virtual Private Cloud concepts, networking, Kubernetes, and High Availability Computing. (20 hour YouTube course):  https://www.freecodecamp.org/news/google-cloud-digital-leader-certification-study-course-pass-the-exam-with-this-free-20-hour-course/ 3. React Router 6 just came out a few months ago, and freeCod