Equally distribute spacing using Auto Layout visual format string - cocoa-touch

Is it possible to equally distribute left and right space for b in #"|-[a(5)]-[b(8)]-[c(5)]-|" using visual format strings?

No.
However, you can do it with visual format and a manual constraint creation. Change your VFL string to:
#"|-[a(5)]->=0-[b(8)]->=0-[c(5)]-|"
This says that you're not too concerned about the actual size of the spaces between a and b, and b and c.
Now, create a constraint pinning the center of b to the center of the superview using constraintWithItem:... (I'm typing this on a phone so forgive me for not spelling out the whole method).
This, coupled with your flexible spacing, will give even spaces to the left and right of b.

Apple's Auto Layout Guide suggests using "spacer views". Here's the solution for laying out your three views with equal spacing horizontally:
// create views dictionary
NSMutableDictionary *viewsDictionary = [NSMutableDictionary dictionary];
[viewsDictionary addEntriesFromDictionary:NSDictionaryOfVariableBindings(viewA, viewB, viewC)];
// create 4 spacer views
for (int i = 0; i < 4; i++) {
UIView *spacerView = [[UIView alloc] init];
spacerView.hidden = YES;
[self addSubview:spacerView];
[viewsDictionary setObject:spacerView
forKey:[NSString stringWithFormat:#"spacer%d", i + 1]];
}
// disable translatesAutoresizingMaskIntoConstraints in views for auto layout
[viewsDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop)
{
[obj setTranslatesAutoresizingMaskIntoConstraints:NO];
}];
// add constraints
[superview addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:
#"|[spacer1(>=0)][viewA][spacer2(==spacer1)][viewB][spacer3(==spacer1)][viewC][spacer4(==spacer1)]|"
options:kNilOptions
metrics:nil
views:viewsDictionary]];
Note that spacer1's width is set to be more than 0. Subsequent spacer views are set to have equal widths with spacer1.

Related

How do I get objects that I set in my NSMutableDictionary and make a touch interface?

I am trying to make grid with touch capabilities using a NSMutableDictionary. I created a coordinate grid (_coordinateRecords being my dictionary) using this in the viewDidLoad function of the view controller:
[self _loopOnCellWithIterator: (^(int iLooper, int jLooper)
{
int cellWidth = 10;
int space = 1;
[_coordinateRecords setObject: [NSIndexPath indexPathForRow: iLooper inSection: jLooper] forKey: [NSValue valueWithPointer: redCell]];
redCell = [[[UIView alloc] initWithFrame: CGRectMake(iLooper * (cellWidth + space) + 24.25,
jLooper * (cellWidth + space) + 110,
cellWidth, cellWidth)] init];
_cells[iLooper][jLooper] = redCell;
[redCell setBackgroundColor: [UIColor blueColor]];
[[self view] addSubview: redCell];
})];
using this block:
- (void)_loopOnCellWithIterator: (void(^)(int iLooper, int jLooper))iterator
{
for (int iLooper = 0; iLooper < kCellSize; iLooper++)
{
for (int jLooper = 0; jLooper < kCellSize; jLooper++)
{
iterator(iLooper, jLooper);
}
}
}
So, I was wondering how I can call upon the objects in the dictionary that I set.
I'm not exactly sure what you're asking, but two things may help:
You access the values in a dictionary using keys. The method -objectForKey: takes a key and returns the associated object. You're expected to know what the keys are, but there's also a method that provides an array containing all the keys.
If you're having a hard time mapping from grid coordinates to dictionary keys, perhaps you're using the wrong data structure. An array may be more suitable since its easy to convert from coordinates to indices.

NSAutoLayout: How to add element dynamically

I need to add an undefined number of NSButton to an NSView in code. The problem is that I can' t use constraintsWithVisualFormat: because i don' t know the name of the NSButton and also the number of button that I have. Anyone have a solution? Thanks!
When using constraintsWithVisualFormat:, you need to know the names of the variables that point to your NSButtons only if you use NSDictionaryOfVariableBindings to create the dictionary of views. You could just as easily build your own dictionary using whatever keys you like.
If your buttons are stored in an array, you can iterate through them and create constraints between each of them:
for ( int i = 1 ; i < buttonArray.count ; i++ ) {
NSDictionary* views = #{ #"buttonOne":buttonArray[i-1] , #"buttonTwo":buttonArray[i] } ;
NSArray* constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"[buttonOne]-[buttonTwo]" options:0 metrics:nil views:views] ;
// Use the constraints.
}

Book layout NSTextFields -- going back pages?

I have two NSTextfields in a book layout, and I can't figure out a fast way to go back to the previous 'page'. Book size, font size, line size all change, so the string of text for the previous page has to be calculated on the fly. Picture:
The NSTextfields each have one NSTextContainer, and they share a NSLayoutManager and NSTextStorage.
Going forward is easy: I take the character range of the visible text, and then create a substring starting from the next character along.
My going back method is a kludge. I figure out the maximum amount of characters that can be visible at once. I then make a string to that length, with the last character the one I want in the bottom right corner of the book. I then loop: removing characters from the start, checking what is visible each time until the character I want is in the bottom right. This is very, very, slow.
Can anyone suggest a faster way to do what I want to achieve? I had the thought of using scrollRangeToVisible, but I couldn't figure out how to set up a NSScrollView for this layout.
Can anyone help?
Textcontainers are set up like this:
-(void)setupTextViews {
articleString = [[NSAttributedString alloc] init];
articleStringPortion = [[NSAttributedString alloc] init];
bookTextStorage = [[NSTextStorage alloc] init];
bookLayoutManager = [[NSLayoutManager alloc] init];
[[self bookTextStorage] addLayoutManager:bookLayoutManager];
leftColumnRect = NSZeroRect;
rightColumnRect = NSZeroRect;
NSDivideRect(bookRect, &leftColumnRect, &rightColumnRect, NSWidth(bookRect) / 2, NSMinXEdge);
// First column
{
NSTextContainer *textContainer = [[NSTextContainer alloc] initWithContainerSize:leftColumnRect.size];
leftColumnTextView = [[CRMouseOverTextView alloc] initWithFrame:leftColumnRect textContainer:textContainer];
[leftColumnTextView setDrawsBackground:NO];
[leftColumnTextView setEditable:NO];
[leftColumnTextView setup];
[bookView addSubview:leftColumnTextView];
[bookLayoutManager addTextContainer:textContainer];
[textContainer release];
[leftColumnTextView release];
}
// Second column
{
NSTextContainer *textContainer = [[NSTextContainer alloc] initWithContainerSize:rightColumnRect.size];
rightColumnTextView = [[CRMouseOverTextView alloc] initWithFrame:rightColumnRect textContainer:textContainer];
[rightColumnTextView setDrawsBackground:NO];
[rightColumnTextView setEditable:NO];
[rightColumnTextView setup];
[bookView addSubview:rightColumnTextView];
[bookLayoutManager addTextContainer:textContainer];
[textContainer release];
[rightColumnTextView release];
}
}
There's no point posting my awful going backwards code, but I'm using this method I found to figure out what is visible each time:
-(NSRange)getViewableRange:(NSTextView *)tv {
NSLayoutManager *lm = [tv layoutManager];
NSRect visRect = [tv visibleRect];
NSPoint tco = [tv textContainerOrigin];
visRect.origin.x -= tco.x;
visRect.origin.y -= tco.y;
NSRange glyphRange = [lm glyphRangeForBoundingRect:visRect inTextContainer:[tv textContainer]];
NSRange charRange = [lm characterRangeForGlyphRange:glyphRange actualGlyphRange:nil];
return charRange;
}
I'm not sure this is the answer you're looking for, but if it were me, I'd probably just "cache" a bunch of those character ranges, for all the previous pages that have been viewed. You probably wouldn't even have any problem storing them all for a book with a lot of pages. Of course, then you still have to use your kludgy code for when the user re-sizes the text, or whatever. (Either that, or you could re-calculate from some suitable starting point... say the beginning of the book if it's fast enough, or the beginning of a chapter or something. Then you just find the page(range) that contains the text that is already being displayed and show the previous one.)

Appending to NSTextView

I've got an NSTask (with an NSPipe set up) running in the background and I want to output the contents, as they're coming in, in an NSTextView (output).
The code I'm using is :
NSMutableAttributedString* str = [[NSMutableAttributedString alloc] initWithString:s];
//[str addAttribute:NSForegroundColorAttributeName value:[NSColor whiteColor] range:NSMakeRange(0, [str length])];
[[output textStorage] appendAttributedString:str];
[output scrollRangeToVisible:NSMakeRange([[output string] length], 0)];
Issues :
When there is a lot of data appending, the view seems like "flashing"... and not working properly.
Given that the NSTextView is on a Sheet, NO CONTENTS seem to be appearing when the mouse pointer is elsewhere other than hovering above the NSTextView
Why is that, although I've set the color/insertion color/etc of the NSTextView, this doesn't seem to apply to newly inserted text?
What's the suggested way of appending (+scrolling) on an NSTextView?
Thanks!
Remember that user interface elements, and this includes NSTextView, do their magic on the main thread. If you're attempting to add information to the text view, that's where you'd best be doing it. Here's how:
[[output textStorage] performSelectorOnMainThread:#selector(appendAttributedString:)
withObject:str
waitUntilDone:YES];
I'd address your third point, but frankly, that's a thing of which I'm still very much a student.
To address your fourth point, it would appear you've got that figured out; just combine your append and scroll actions. But just like changing the contents of textStorage, you want to be sure you're doing this on the main thread. Since -scrollRangeToVisible: doesn't take an object for its argument, you have to do this a bit differently:
dispatch_async(dispatch_get_main_queue(), ^{
[output scrollRangeToVisible:NSMakeRange([[output string] length], 0)];
});
My first example notwithstanding, you could place your call to -appendAttributedString: inside that block as well:
dispatch_async(dispatch_get_main_queue(), ^{
[[output textStorage] appendAttributedString:str];
[output scrollRangeToVisible:NSMakeRange([[output string] length], 0)];
});
Regarding the recommended way of appending to the NSTextView: You're doing quite well with appendAttributedString:, but it's recommended to shield it inside shouldChangeTextInRange, then a beginEditing, appendAttributedString, and finally endEditing:
textStorage = [textView textStorage];
if([textView shouldChangeTextInRange:range replacementString:string])
{
[textStorage beginEditing];
[textStorage replaceCharactersInRange:range withAttributedString:attrStr];
// or if you've already set up the attributes (see below)...
// [textStorage replaceCharactersInRange:range withString:str];
[textStorage endEditing];
}
I'd strongly suggest replacing scrollRangeToVisible: by scrollToPoint:, as scrollRangeToVisible: will cause a lot of flickering and it will also gradually become slower as you move 'down the range'.
A quick-and-dirty way could be something like this:
- (void)scrollToBottom
{
NSPoint pt;
id scrollView;
id clipView;
pt.x = 0;
pt.y = 100000000000.0;
scrollView = [self enclosingScrollView];
clipView = [scrollView contentView];
pt = [clipView constrainScrollPoint:pt];
[clipView scrollToPoint:pt];
[scrollView reflectScrolledClipView:clipView];
}
I let constrainScrollPoint do all the calculation work.
I do this, because my calculations failed anyway (those suggested by Apple and others, that used visRect/docRect coordinates, produced unreliable results).
reflectScrolledClipView is also important; it updates the scroll bar so it has the correct proportion and position.
You might also find it interesting to know when scrolling has occurred. If so, subscribe to both NSViewBoundsDidChangeNotification and NSViewFrameDidChangeNotification. When one of them occurs, the scroll bar position most likely changed (investigate [textView visibleRect] and [textView bounds]).
I see you also have trouble with the text-attributes. So did I for a long time.
I found that appending an attributed string would help quite a lot, but it still wasn't enough for the text being typed.
..Then I found out about typingAttributes.
When setting up your NSTextView, for instance in an -awakeFromNib, you can pick what you like from the following...
NSMutableParagraphStyle *paragraphStyle;
float characterWidth;
NSFont *font;
uint32_t tabWidth;
NSMutableDictionary *typingAttributes;
tabWidth = 4;
font = [NSFont fontWithName:#"Monaco" size:9.0];
paragraphStyle = [[textView defaultParagraphStyle] mutableCopy];
if(NULL == paragraphStyle)
{
paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
// or maybe:
// paragraphStyle = [NSParagraphStyle new];
}
characterWidth = [[font screenFontWithRenderingMode:NSFontDefaultRenderingMode] advancementForGlyph:(NSGlyph)' '].width;
[paragraphStyle setDefaultTabInterval:(characterWidth * (float) tabWidth];
[paragraphStyle setTabStops:[NSArray array]];
typingAttributes = [[textView typingAttributes] mutableCopy];
if(NULL == typingAttributes)
{
typingAttributes = [NSMutableDictionary new];
}
[typingAttributes setObject:paragraphStyle forKey:NSParagraphStyleAttributeName];
[typingAttributes setObject:font forKey:NSFontAttributeName];
[textView setTypingAttributes:attributes];
...It's way more than you probably need, but it shows how you can set the font, the tab width and the typing attributes.
NSForegroundColorAttributeName might also be interesting for you (as well as some other attributes, type NSForegroundColorAttributeName in Xcode and option-double-click on it, then you'll see some more attributes (you can command-double-click as well; this takes you to the definition in the header file).

Using a variable inside a variable objective-C

EDIT: {
No longer need help!
I used tags and a bunch of loops to reference them at anytime.
I never knew you could store so many images in one UIImageView!
}
I have an application that deals with a a lot of images and what i want to do is use an integer inside the variable name so i don't have to write code for each image. Ex:
- (void)addIconClicked {
if (icons < 28) {
icons += 1;
}
if (icons == 2) {
UIImageView * iconImage2 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"icon1.png"]];
//modify image view like setting the frame, setting the title, etc... (not important)
}
else if (icons == 3) {
// iconImage 3 set up
}
this continues all the way up to iconImage26!
so i was wondering if i could use the integer "icons" as part of the variable name so i don't have to run the code 26 different times!!
Ex:
- (void)addIconClicked {
if (icons < 28) {
icons += 1;
}
/*some how insert the "icons" int where (icons) is. Like NSString uses
stringWithFormat ([NSString stringWithFormat:#"%i", icons])*/
UIImageView * iconImage(icons) = [[UIImageView alloc] initWithImage:[UIImage imageNamed:[NSString stringWithFormat:#"icon%i.png", icons]]];
}
-----edit----- (to clarify what i want)
my problem is that i want to make an unlimited amount of image views but if i use the same variable more than once, it would show up on the view fine, but it would be released and i can't edit it anymore.
Ex:
- (void)addIconClicked {
if (icons < 28) {
icons += 1;
}
NSString * iconName = [NSString stringWithFormat:#"icon%i.png", icons];
//if i ran this 100 times, 100 images would show up, but i can no longer edit any of them except the newest.
iconImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:iconName]];
}
so i need to know how to make an unlimited amount of images that i can still edit individually.
i thought i might be able to use the integer's value inside a variable name when i create it, but i guess i can't:(
so if anyone knows, please explain!!
so to sum it up...
my exact problem is that i want to create an unlimited number of UIImageView's using a different image each time.
You think i could just use 1 variable for all of the images (which would show up) but then i can't edit them at all because they are released.
I just need a way to create an unlimited amount of Global UIImageViews that i can edit and access at any time.
The best way to do this is with an array.
NSMutableArray* allIcons = [NSMutableArray array]; //First, you'll need this empty array.
//Then, when you create your icon...
NSString* iconName = [NSString stringWithFormat:#"icon%lx.png",icons];
UIImageView * newIcon = [[UIImageView alloc] initWithImage:[UIImage imageNamed:iconName]];
[allIcons addObject:newIcon]; //This adds the icon at the next index, starting at 0.
Then, when you use your icons, instead of icon1, icon2, and so on, use [allIcons objectAtIndex:0], [allIcons objectAtIndex:1], and so on.
This is a pretty common concept--you should consider checking out a few beginning Cocoa tutorials; you might find it in use.
You can use an array for storing UIImageView references. Or you can set their tags and find them later using viewWithTag.
EDIT: Ok I am home now and I can explain:
Use tagging:
UIImageView * anImageView= [[UIImageView alloc] initWithImage:[UIImage imageNamed:[NSString stringWithFormat#"icon%i.png",icons]]];
anImageView.tag=icons;
[parentView addSubview: anImageView];
UIImageView *theOneYouWant = (UIImageView)[parentView viewWithTag:someTag];
or use a c-array of UIImageView pointers to store references if you don't mind scope:
UIImageView * imageViews[28]; // put this into class interface
imageViews[icons]= [[UIImageView alloc] initWithImage:[UIImage imageNamed:[NSString stringWithFormat#"icon%i.png",icons]]];