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.)
Related
In an NSTextStorage I insert time strings at the current pointer location like this :
NSMutableAttributedString* attrString = [[NSMutableAttributedString alloc] initWithString: #"00:00:00"];
[attrString autorelease];
int pos = [self selectedRange].location;
[[self textStorage] insertAttributedString: attrString atIndex:pos];
So far so good, it works perfect. But now I want to position the pointer at the beginning of the next line. Obviously this is right after the next return char.
Now how to find the next return char in textStorage and position the pointer there ?
I have not found any hint in the web for this task. Please help ...
You won't find a method to set the cursor (more precise: selection with zero length) in NSTextStorage's API, because it is a storage, having no selection. The selection is a property of the text view. This is the result of MVC. Simply do a check: You can have many text views displaying the same text. Obviously each one needs its own selection.
What you have to do is to get the position of the next paragraph (this is better than searching for \n) and set this as a selection for the text view.
NSMutableAttributedString* attrString = [[NSMutableAttributedString alloc] initWithString: #"00:00:00"];
[attrString autorelease]; // You should *really* use ARC
int pos = [self selectedRange].location;
[[self textStorage] insertAttributedString: attrString atIndex:pos];
// Get the next paragraph
NSString *text = self.textStorage.string;
NSRange restOfStringRange = NSMakeRange( pos, [text length]-pos );
NSUInteger nextParagraphIndex;
// You have to look to the start of the next paragraph
[text getParagraphStart:&nextParagraphIndex end:NULL contentsEnd:NULL forRange:restOfStringRange];
NSTextView *view = self.textView; // Or where ever you get it from
view.selectedRange = NSMakeRange(nextParagraphIndex, 0);
Typed in Safari, not tested, just to show the basic approach.
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.
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).
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]]];
PanelImage is Global allocED and initiated in a method called from ViewDidLoad
PanelImage = [[UIImageView alloc]initWithImage:[UIImage imageNamed:#"panel.png"];
[self.view addsubView:PanelImage];
My image comes up properly but
when i try to hide it using
PanelImage.hidden = YES;
it Doesn't hide
i test the property using
if(PanelImage.hidden) but i doesnt pass
i also printed it
NSLog(#"panel is hidden %d",PanelImage.hidden);
it outputs "Panel is hidden 0" even after setting it by
PanelImage.hidden = 1;
please help,atleast tell me some technique to debug it.
there are lots of methods which are using PanelImage.hidden,it used to work before 2days.now only point where PanelImage.hidden works is the custom initialization function(called from ViewDidLoad)
also this is piece of a very big code.
after lots of debugging i came to a point where PanelImage.hidden goes inoperable
PanelImage.hidden = YES;//works till here here
[self GetSymbolAttr];//wont work after this function is called
definition of GetSymbolAttr
-(void)GetSymbolAttr
{
int tmp = 0;
NSArray* PosAndSizeArrForCurrSlot = [[PosAndSizeArr objectAtIndex:SlotId] componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:#" "]];
for(NSString* values in PosAndSizeArrForCurrSlot)
PositionAndSize[tmp++] = [values intValue];
}
as you can see nothing is happening in GetSymbolAttr which will make Pattern.hidden go inoperatble
if PanelImage.hidden = YES; is now working than you can do its alpha.set to zero . PanelImage.alpha = 0 ;
try this:
UIImageView *PanelImage = [[UIImageView alloc] initWithImage:[UIImage ...]];
[self.view addsubView:PanelImage];
I didn't find initWithImageNamed method with UIImageView !
Do you set PanelImage.hidden = YES in the same metod, where you're doing UIImageView *PanelImage = [[UIImageView alloc]initWithImageNamed:#"Panel.png"];? Or haven't you class field with the same name PanelImage?