Sunday 21 August 2011

Reading from external controllers with CoreMIDI

As a relative newcomer to Cocoa and OSX some of Cocoa's frameworks seem very good, some I've looked at are what I expected, but recently I had to do some MIDI work and frankly, CoreMIDI is just weird and confusing. The documentation is limited, to say the least, and really not what I've come to expect from Apple. But I got what I needed done and so this post, more than some others, is to serve two purposes. One, to help me remember what I worked out, and two, maybe it'll help someone else.

I have a few MIDI controllers lying around, and what I wanted to do was to read the MIDI data as the various knobs and faders and buttons are manipulated and have it tell me what was happening.

In CoreMIDI we create input and output ports, and connect them to other ports or endpoints. For our case we want to create an input port, and connect this to the external MIDI controller. When we create an input port we also give it a callback function that will get called when the MIDI controller sends data.

The first step to create an input port is to create a MIDIClient

MIDIClientRef midiClient;

OSStatus result;
    
result = MIDIClientCreate(CFSTR("MIDI client"), NULL, NULL, &midiClient);
if (result != noErr) {
    NSLog(@"Error creating MIDI client: %s - %s",
        GetMacOSStatusErrorString(result), 
        GetMacOSStatusCommentString(result));
        return;
}

The first parameter gives the MIDIClient a name and as we're working with CoreFoundation here we use the CFSTR macro. The second and third parameters are for receiving notifications when the MIDI system changes, but we don't care about that here and so just pass NULL. The final parameter is for obtaining our MIDIClientRef. A lot of the CoreMIDI functions return OSStatus to indicate success or failure, and it's a good idea to check there wasn't any errors before continuing. In the following code snippets, I'll leave out the error checking as it's the same as the check above to save space, but it is present in the example code.

Once we have a MIDIClient, we can then create an input port.

MIDIPortRef inputPort;
result = MIDIInputPortCreate(client, CFSTR("Input"), midiInputCallback, NULL, &inputPort);

The first parameter it takes is our MIDIClientRef that we created earlier, and the second is the name of the port. At the end, the final parameter is for obtaining our newly created port. The third and fourth parameters are the callback for whenever there is MIDI data available on the port. The third is the function and the fourth is data that will be passed into that function as context. Here we don't have any context data, but in a more complicated objective C program we would pass usually pass self or some other object. It is in this callback that the MIDI parsing happens. Care needs to be taken as it is also called on a separate, high priority thread, which means that any data access needs to be done in a thread safe manner, and any UI updating needs to be triggered on the main UI thread using performSelectorOnMainThread.

So what does this callback function look like then? Something like this:

static void
midiInputCallback (const MIDIPacketList *list,
                   void *procRef,
                   void *srcRef)
{
    NSLog(@"midiInputCallback was called");
}

We'll go into detail about what goes into it, and what the various parameters are for later, as first I want to get the rest of the ports hooked up.

So we want to get an endpoint on the external MIDI device and functions such as MIDIGetSource seem perfect for it, but they take an index to where the device is in the system. This is useful when you want to offer a list of devices for the user to choice the device from, but makes things more complicated for us. For this initial example we're going to use MIDIObjectFindByUniqueID but it needs a unique ID. To get this we need a helper program first.

A Brief Interlude

Make a new command line project, I called it MidiLister. Basically it is going to list all the MIDI devices and print their Unique IDs. First we need to add the CoreMIDI framework to it. If you select the MidiLister target, and go to the Build Phases page. Then in the section titled Link Binary With Libraries, click the plus symbol, type coremidi and select it.

Now in the main.m file import CoreMIDI/CoreMIDI.h and add the following code to the main function:

ItemCount numOfDevices = MIDIGetNumberOfDevices();
    
for (int i = 0; i < numOfDevices; i++) {
    MIDIDeviceRef midiDevice = MIDIGetDevice(i);
    NSDictionary *midiProperties;
        
    MIDIObjectGetProperties(midiDevice, (CFPropertyListRef *)&midiProperties, YES);
    NSLog(@"Midi properties: %d \n %@", i, midiProperties);
}

We're getting all the MIDI devices that the system knows about, and printing out their properties. If you build, run it, and look at the output somewhere in the output will be the MIDI device you want to use, and it will print something like this

    entities =     (
                {
            destinations =             (
                                {
                    uniqueID = "-2123048758";
                }
            );
            embedded = 0;
            maxSysExSpeed = 3125;
            name = LPD8;
            sources =             (
                                {
                    uniqueID = 1759718006;
                }
            );
            uniqueID = "-2133248137";
        }
    );
    image = "/Library/Audio/MIDI Devices/Generic/Images/USBInterface.tiff";
    manufacturer = "AKAI professional LLC";
    model = LPD8;
    name = LPD8;


There may be a lot of devices and there are quite a lot of unique IDs for each device, each entity has a one and the device has one of its own. Which one do we want? Well, if you look through the output for something related to the MIDI controller you want to use. I knew that the model name for my controller was the Akai LPD8, and in this case we're wanting to receive data from the controller, so we want to use a source entity. This device only has one source, and its UniqueID is 1759718006. It will probably be different on your machine and for your controllers. Write the UniqueID down, and go back to the original project.

Now that we can use MIDIObjectFindByUniqueID to get the endpoint to use with this.

MIDIObjectRef endPoint;
MIDIObjectType foundObj;
    
result = MIDIObjectFindByUniqueID(1759718006, &endPoint, &foundObj);

Obviously in the above code, you should replace the uniqueID with the one for your device. We don't actually care about the foundObj, what we're interested in is the endPoint parameter and once we have that, we can link the input port that we created earlier to this endpoint.

result = MIDIPortConnectSource(inputPort, endPoint, NULL);

This function is quite straight forward, it connects the port that we created earlier (inputPort) to the end point that we got from our device (endPoint) and the last parameter is for some context data. Finally by adding a CFRunLoop so that any input events will be processed we will see that our callback is triggered whenever you do something on the MIDI controller.

CFRunLoopRun();

Processing MIDI Events

Now that we have a program that can read MIDI events, we need to work out what the MIDI data is actually telling us. Unfortunately CoreMIDI is only used for configuring, sending and receiving MIDI data, it doesn't have anything to help with creating or parsing that MIDI data. Luckily the upside is that MIDI is a very simple protocol and there are various places online that describe it (without you needing to spend big bucks on the official MIDI spec). CoreMIDI passes the data to our callback function in a MIDIPacketList. Remember this was our callback function's protocol:

static void 
midiInputCallback (const MIDIPacketList *list,
                   void *procRef,
                   void *srcRef)

The MIDIPacketList parameter contains all the data related to the control change, and the two void * parameters are two pieces of context data. The first (procRef) is the context data that was set when the port was created via MIDIInputPortCreate, the second (srcRef) is the context data that was set when the port was connected to the endpoint with MIDIPortConnectSource

To process a MIDIPacketList we iterate through the list, processing the MIDIPackets that are contained in it. A MIDIPacket contains at least one complete MIDI message, except for Sysex messages which can be spread over multiple packets.

A MIDI message consists of a status byte, and then fixed number of bytes, depending on the message (apart from SysEx messages again but we'll ignore them for now). The high bits of the status byte tell us the message type, and the low bits tell us the channel number. So, for example, the status 0x83 tells us that a Note Off message was received from channel 4 (in MIDI messages the channels are numbered from 0-F, but in MIDI terminology they are numbered from 1-16, so 3 is really the fourth channel).

To find out the length of each message, http://home.roadrunner.com/~jgglatt/tech/midispec.htm is a pretty good guide. Looking at the 0x83 message again, we can see that Note Off has 2 bytes of data afterwards. This way we can iterate over the data contained in the MIDIPacket finding all the messages and acting on them.

As mentioned above, the exception to all this is the SysEx message. This message starts with 0xF0 (SysEx Start) and all the following data until the message 0xF7 (SysEx End) is part of the SysEx message. This means it could be over multiple MIDIPackets, but all the data for a SysEx message will not be spread over multiple MIDIPacketLists. Also, if there is a SysEx message in a MIDIPacket there will not be any other messages in that MIDIPacket. This simplifies processing SysEx messages considerably.

Here is an example of how you could process some MIDI messages in the midiInputCallback function. I'm not claiming this is perfect ands there's probably lots of places where it could be improved. If you know an improvement or spot an error, feel free to leave a comment.


#define SYSEX_LENGTH 1024

We define a maximum length for a SysEx message.

void midiInputCallback (const MIDIPacketList *list,
                        void *procRef,
                        void *srcRef)
{
    bool continueSysEx = false;
    UInt16 nBytes;
    const MIDIPacket *packet = &list->packet[0];

We get the first MIDIPacket in the list. Although we use ->packet[0] to get the first packet, the other packets are not accessed by ->packet[1], [2] etc. We need to use MIDIPacketNext,  as you'll notice at the end of the function.

    unsigned char sysExMessage[SYSEX_LENGTH];
    unsigned int sysExLength = 0;
    
This is just a buffer for collecting the SysEx messages. 

We start by going through all of the MIDIPackets in the MIDIPacketList

    for (unsigned int i = 0; i < list->numPackets; i++) {
        nBytes = packet->length;
        

We want to check if we're gathering a SysEx message that is spread over many MIDIPackets. If it is, then we need to copy the data into the message buffer.

        // Check if this is the end of a continued SysEx message
        if (continueSysEx) {
            unsigned int lengthToCopy = MIN (nBytes, SYSEX_LENGTH - sysExLength);
            // Copy the message into our SysEx message buffer,
            // making sure not to overrun the buffer
            memcpy(sysExMessage + sysExLength, packet->data, lengthToCopy);
            sysExLength += lengthToCopy;

Now we've copied the data, we check if the last byte is the SysEx End message.
            
            // Check if the last byte is SysEx End.
            continueSysEx = (packet->data[nBytes - 1] == 0xF7);

If we've finished the message, or if we've filled the buffer then we have  a complete SysEx message to process. Here we're not doing anything with it, but in a proper application we'd pass it to whatever acts on the MIDI messages.

            if (!continueSysEx || sysExLength == SYSEX_LENGTH) {
                // We would process the SysEx message here, as it is we're just ignoring it
                
                sysExLength = 0;
            }
        } else {

If we weren't continuing a SysEx message then we need to iterate over all the bytes in the MIDIPacket parsing the messages that are contained in it.

            UInt16 iByte, size;
            
            iByte = 0;
            while (iByte < nBytes) {
                size = 0;
                
                // First byte should be status
                unsigned char status = packet->data[iByte];
                if (status < 0xC0) {
                    size = 3;
                } else if (status < 0xE0) {
                    size = 2;
                } else if (status < 0xF0) {
                    size = 3;
                } else if (status == 0xF0) {
                    // MIDI SysEx then we copy the rest of the message into the SysEx message buffer
                    unsigned int lengthLeftInMessage = nBytes - iByte;
                    unsigned int lengthToCopy = MIN (lengthLeftInMessage, SYSEX_LENGTH);
                    
                    memcpy(sysExMessage + sysExLength, packet->data, lengthToCopy);
                    sysExLength += lengthToCopy;
                    
                    size = 0;
                    iByte = nBytes;

                    // Check whether the message at the end is the end of the SysEx
                    continueSysEx = (packet->data[nBytes - 1] != 0xF7);
                } else if (status < 0xF3) {
                    size = 3;
                } else if (status == 0xF3) {
                    size = 2;
                } else {
                    size = 1;
                }
            
                unsigned char messageType = status & 0xF0;
                unsigned char messageChannel = status & 0xF;

Now we know the size of each message, what type it is, and what channel it was received on and we can pass it off to something that will parse it. For this example, here is some code that just prints the message and the values. Ideally this would happen on a low priority thread so that it doesn't block the thread that receives the MIDI messages, but for this example it doesn't matter too much.
    
                switch (status & 0xF0) {
                    case 0x80:
                        NSLog(@"Note off: %d, %d", packet->data[iByte + 1], packet->data[iByte + 2]);
                        break;
                        
                    case 0x90:
                        NSLog(@"Note on: %d, %d", packet->data[iByte + 1], packet->data[iByte + 2]);
                        break;
                        
                    case 0xA0:
                        NSLog(@"Aftertouch: %d, %d", packet->data[iByte + 1], packet->data[iByte + 2]);
                        break;
                        
                    case 0xB0:
                        NSLog(@"Control message: %d, %d", packet->data[iByte + 1], packet->data[iByte + 2]);
                        break;
                        
                    case 0xC0:
                        NSLog(@"Program change: %d", packet->data[iByte + 1]);
                        break;
                        
                    case 0xD0:
                        NSLog(@"Change aftertouch: %d", packet->data[iByte + 1]);
                        break;
                        
                    case 0xE0:
                        NSLog(@"Pitch wheel: %d, %d", packet->data[iByte + 1], packet->data[iByte + 2]);
                        break;
                        
                    default:
                        NSLog(@"Some other message");
                        break;
                }
                
                iByte += size;
            }
        }

As mentioned above, to get the next MIDIPacket you need to use MIDIPacketNext.

        packet = MIDIPacketNext(packet);
    }
}


And thats how you read data from a MIDI controller. Hopefully this is useful to some people.

Apple documentation for CoreMIDI - https://developer.apple.com/library/mac/#documentation/MusicAudio/Reference/CACoreMIDIRef/MIDIServices/

Saturday 13 August 2011

Laying out interfaces automatically with Corelayout Part 2 - Layout Format Language

In the last post on Corelayout we looked at how you can set up autolayout with the Interface Builder. There are times, however, when the layout needs to be done by hand in the code and in Cocoa there is a layout format language that aims to simplify the process of creating constraints.

This language looks something like "|-20-[button]-20-|". The superview is marked by the pipe symbol '|' and this string tells the Corelayout system that a control called button is placed 20 pixels away from the left and right edges of the superview. To set the vertical layout, the layout string should start with "v:".  More complicated constraints can be added to the string as well by adding the rules inside parentheses: "|-20-[button(<=350)]-20-|" will restrict the button width to <= 350 pixels.


Lets make a simple example. First, we'll need a function to create a button



- (NSView *)buttonWithLabel:(NSString *)title
{
    NSButton *button = [[[NSButtonalloc] init] autorelease];
    [button setBezelStyle:NSRoundedBezelStyle];
    [button setTitle:title];
    [button setTranslatesAutoresizingMaskIntoConstraints:NO];
    
    return button;
}

Notice that we do not specify a size for the button via initWithFrame: we're just going to leave the button to figure out its own size for itself. This is called its 'intrinsic size'. For a button, its intrinsic height is just enough to display it's child control, you don't normally want a button to be higher than its child control needs to be so we say that it "strongly hugs' it's content vertically, but the intrinsic width of a button can really be anything, so long as it is larger than its content width. In this case we say that it "weakly hugs" it's content horizontally.

The other thing to notice is the call to setTranslatesAutoresizingMaskIntoConstraints:. This call tells Cocoa to ignore the autoresizing mask when working out the constraints as the autoresizing mask may produce a conflicting constraint and we're going to be doing all the constraints ourselves.

As this is a very simple example, the rest of the code is going to go into the applicationDidFinishLaunching: method of the application delegate and it will set up a simple window with 2 buttons in it.


First, we create the buttons and add them to the parent window

NSView *button = [self buttonWithLabel:@"Test button"];
NSView *button2 = [self buttonWithLabel:@"Hello button"];

NSView *view = [window contentView];
[view addSubview:button];
[view addSubview:button2];

The constraints system takes a dictionary so that it can link controls named in the format string to controls. The names in the format string are used as the keys in this dictionary and the function NSDictionaryOfVariableBindings is useful here.  It takes a list of objects and creates a dictionary with those objects with the variable names as keys.

NSDictionary *views = NSDictionaryOfVariableBindings(button, button2);

will create a dictionary with the key @"button" that points to the object button, and a key @"button2" which points to the object button2.

Finally we just need to add the constraints to the superview:

[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-20-[button]-[button2]-20-|"
                    options:NSLayoutFormatAlignAllBaseline
                    metrics:nil
                      views:views]];
[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[button]-55-|"
                    options:0
                    metrics:nil
                      views:views]];

The first constraint sets the horizontal layout, and it says that the control called 'button' is to be placed 20 pixels away from the left hand edge of the superview (which in this case is the contentview of the window), and there should be spacing the size of the default Cocoa spacing between it and 'button2', which should in turn be placed 20 pixels away from the right hand edge of the superview. The options parameter says that all the controls should be aligned along their baselines. The final two parameters; metrics and views, are dictionaries that were mentioned above, to allow the layout system to match values in the format string to real controls. We will look at what the metrics parameter does later on.

The next constraint is the vertical one, and it says that 'button' should be placed the Cocoa default spacing away from the top edge of the window, and 55 pixels away from the bottom edge. We don't need to provide any contraints for button2's vertical placement as it has been aligned with the baseline of 'button'.

And that is all that you need to layout a simple window with resizing controls, next we'll look at some more complicated layout strings.

As in the example in the first article, when you resize the window one of the buttons resizes while the other remains fixed in width. To fix this we need to add an equal width constraint to the format string. If you change the format string for the horizontal constraint to "|-20-[button(==button2)]-[button2]-20-|" and recompile it, you will see that when you expand the window, the two button widths match. And, as in the first article, we can control that the first button will stop expanding at 350pixels, with the second button continuing to expand by setting another constraint to the format string and setting the priority: "|-20-[button(==button2@20,<=350)]-[button2]-20-|"

So, as you can see, we can add as many constraints as we need, separated by commas, in parentheses after the identifier. We can also add constraints to the spacings. For an example, lets make the vertical layout have an expandable space between 20 and 50pixels in size before the buttons. To do this, set the vertical constraint layout to:

"V:|-(>=20,<=50)-[button]-55-|"

We skipped over the metrics parameter earlier, but it works similarly to the views parameter. If you pass in a dictionary that maps a metric name to an NSNumber then that name can be used in place of a number. If we created a metrics dictionary like so:

NSNumber *topHeight = [NSNumber numberWithFloat:55.0];
NSDictionary *metrics = NSDictionaryOfVariableBindings(topHeight);

and passed it into the constraintsWithVisualFormat:options:metrics:views method then we can write a format string that refers to topHeight: "V:|-topHeight-[button]-55-|". When the format string is parsed, the key topHeight will be looked up in the metrics dictionary, and the NSNumber will be used instead. As NSNumbers are immutable, it isn't possible to change what a metric represents.

All in all though, the visual format language seems like an interesting way to describe the layout of an interface, and will certainly make laying out interfaces by hand easier for me at least, as I never really liked having to sit down and work out the frame sizes for all the controls before writing the code. I'm sure I've not covered everything, and there's more to discover about Corelayout as I go.


Other articles about Corelayout

Tuesday 9 August 2011

NSCollectionView Redux

In the previous post on NSCollectionView I was unable to build the NSCollectionViewItem's NSView in Interface Builder so I built it by hand. Thanks to a post on Stack Overflow (http://stackoverflow.com/questions/6866798/binding-to-nscollectionviewitem-crashes-xcode/6961261#6961261) I was shown how to do it. So here it is in a fuller example.

Starting with the same data item we had the last time:


@interface Item : NSObject {
    NSString *title;
    NSInteger option;
}


@property (readwrite, copy) NSString *title;
@property (readwrite) NSInteger option;


- (id)initWithTitle:(NSString *)_title option:(NSInteger)_option;
@end

And creating a model in the application delegate in a similar way



- (id)init
{
    self = [super init];
    
    items = [[NSMutableArray alloc] init];
    
    int i;
    for (i = 0; i < 20; i++) {
        Item *item = [[Item alloc] initWithTitle:@"test" option:i];
        [items addObject:item];
    }
    
    return self;
}

In the MainMenu.xib when we drag an NSCollectionView into the window, it creates the NSCollectionViewItem and NSView automatically. This time, we only want to delete the NSView as we are going to create a new one.

Create a new file, select View from the User Interface category and name it what you will. In this view we can design our item. 


In this nib file, set the class type of the File's Owner to NSCollectionViewItem. This allows us to bind controls to the representedObject property of NSCollectionViewItem.


I bound the value of the label to representedObject.option and the value of the NSTextField to representedObject.title.


NSCollectionViewItem has a view outlet and this is used whenever it is asked which view is the main view. We need to set this by control dragging from the File's Owner placeholder to the Custom View object, and selecting view.



The final thing we need to do is to tell the NSCollectionViewItem which nibfile to load whenever it needs to create a new view. In the MainMenu.xib file, select the Collection View Item and display the Attributes Inspector. In the Nib Name dropdown either type or select the name of the custom View's nibfile.


Just before we build and run, we need to tell the NSCollectionView where to get its data from. Add an NSArrayController to the MainMenu.xib, and bind the content property of the NSCollectionView to the arrangedObjects property of the NSArrayController.


and bind the content array property of the NSArrayController to the model that we created in the application delegate.


Now if you build and run, the collection view should display the custom item:



It is good to know how to do this now, as doing it by hand felt clunky and awkward, especially to make a nice UI.


Links to the other parts of the NSViewController articles
Part 1:- http://comelearncocoawithme.blogspot.com/2011/07/nsviewcontrollers.html
Part 2:- http://comelearncocoawithme.blogspot.com/2011/07/nsviewcontrollers-part-2.html
Part 3:- http://comelearncocoawithme.blogspot.com/2011/08/nsviewcontrollers-part-3.html

Sunday 7 August 2011

Laying out interfaces automatically with Corelayout Part 1

I come from a GTK background on Linux, so I was surprised to find that Cocoa is a statically laid out UI. GTK has the concept of layout containers and boxes into which you insert controls and tell the box how you want them to be laid out and what to do when the size of the box changes. While it is confusing at first, it very quickly becomes a powerful way to layout a user interface that resizes perfectly when the window or the control size changes. Cocoa has the concept of struts and springs that allow some degree of control over resizing, but it does not give the programmer as much power over window resizing and doesn't handle control resizing at all.

However, the Gtk box model is not perfect, and one of the areas it doesn't work so well is allowing controls to be moved around which complicates animating interfaces which is one area that Cocoa interfaces do very well. So to get around both these problems Apple have added CoreLayout to Lion. Corelayout allows the programmer to add contraints to the interface and describe the relationship between controls and views via these constraints. It is similar to the constraints system used in CoreAnimation but gives the programmer even more control in describing what happens when views change sizes.

Constraints can be created in Interface Builder or in code, however the support in Interface Builder still seems somewhat buggy unfortunately with annoying behaviours and the occasional stack trace dialog. Hopefully future versions will be more stable, the feature seems like it was only added relatively recently to Interface Builder, so save often. We'll first look at how to create constraints in IB, and then move on to creating them by hand in code.

The first thing that needs to be done is to turn on constraints for the nib file. To do this, select the nib file so that the interface builder appears, then click on the file inspector button on the righthand side pane. Then you need to check the option marked "Use Auto Layout".


A warning may come up informing you that you can only run this program on 10.7, but you know that, so just dismiss it.

To demonstrate constraints I just put some simple controls into a window, and drag them until they snap to the blue constraints lines at the edges of the window. These are automatic constraints that are fixed at the correct Cocoa spaces and when the control is snapped to one, it automatically gets constrained. So we expand the second text field to the right until it snaps to the blue constraint line, just the way we did when we created a layout in Cocoa previously, the difference is that now, with CoreLayout turned on, the control is now automatically constrained to always be at that guide line. If we expand the window, the control will move, or expand so that the right hand edge is always the same distance from the window edge.


In the left hand column, under Objects a new object has been added to the hierarchy: "Constraints". If you expand it then you see all the constraints that are present. Ones with a purple icon are automatic constraints, user-added constraints have a blue icon. The default ones for this interface are



We have two vertical spaces, one for each text field that dictates how far the top of the text field is away from the top of the window. We have a horizontal space constraining the left edge of the left text field to the left edge of the window, one doing the same for the right edge of the right text field, and we also have one making the space between the two text field constant as well.

If you select a control, you can see the constraints that act upon it, drawn as lines around it:


and if you select a constraint, then the control (or controls) that are affected by the constraint are highlighted in yellow, and the appropriate constraint line is given a drop shadow to stand out:


There is one final constraint that dictates that the width of the first text field will always be fixed. This means that as the window is expanded in width, the first text field will not expand, but the second will. If you want to test it out, build it and run it, and you can see for yourself. But maybe this default setting is not what you want. Maybe you want both the text fields to expand equally.

Adding Custom Constraints

To do this, you need to add a custom constraint. Select both the text fields, and in Editor->Pin menu select "Widths Equally". If you now look at the constraints list you'll see that the "Width (213)" constraint has been replaced with a "Equal Widths" constraint with a blue icon, and if you select that constraint both text fields turn yellow and the width constraint lines now have an = in a circle on them. Now if you run the program and expand the window, both the fields will expand to fill the space.



But maybe instead of always being equal, you want them to be equal until the left one is 350 pixels wide, and then the right field will keep expanding. So lets add a width constraint to the left hand field. Select it, and in the Editor->Pin menu select "Width". This will add a second user constraint to the field, that says its width must be equal to 213 (or whatever the default width you set). We are able to change the type of the constraint, choosing between equal to, less than or equal to, and more than or equal to. We do this by selecting the constraint and bringing up the Attributes Inspector. If we set the Relation dropdown to "Less than or equal" and set the constant to 350.

(While doing this, you might find that XCode unselects the constraint and reorders the constraints list every time you try to change something. This is quite annoying, but just select the constraint again and continue.)

Now, what happens when you build and run it? If you expand the window, both the text fields will expand until they are 350 pixels wide but then you won't be able to expand the window any more. This is because our two constraints say that the first text field must be less than 350 pixels wide, and that both text fields must be equal width. How can we fix this?

Well, the constraints system has the idea of priority and by default constraints are set to 1,000 which means "Must be fulfilled". If we were to lower one of the priorities to be an optional constraint then it wouldn't need to be fulfilled if it can't be.

So which one should be lowered? Well, the constraint we want to break is the equal width one as we always want the first text field to be less than 350 pixels in width. Set the priority of the "Equal widths" constraint to about 500 and rebuild. Also notice than when a constraint is not "Must be fulfilled" the constraint line becomes broken with dashes.

Now when you build and run, if you expand the window the text fields will remain equal width until the first is 350 and then only the second one will expand.


That's a basic guide to auto layout in Interface Builder. In the next part we'll look at creating constraints in code using the Visual Format Language

Other articles on Corelayout
Part 2:- http://comelearncocoawithme.blogspot.com/2011/08/laying-out-interfaces-automatically_13.html