Saving and restoring NSTextView's position - scroll

I want to be able to save the current text and visible region of an NSTextView and restore it. Using visibleRect and scrollRectToVisible: seems to deliver inconsistent results. If I just do:
- (void)restorePosition
{
NSRect r = [self.textView visibleRect];
[self.textView.layoutManager replaceTextStorage: self.textView.textStorage];
[self.textView scrollRectToVisible: r];
}
the view will stay in position when the view is positioned less than 85 lines from the top, but the further down I go the further off it becomes. At 200 lines from the top it ends up at 277, at 300 it ends up at 408 etc.
Without the replaceTextStorage it works as expected too, but replacing the text and finding the previous place is the whole point. Something about replacing the text causes the line rectangle calculations to go wonky.
Obviously I'm planning to get the new visibleRect and textStorage from a saved object in the real app, but this illustrates the problem with the minimum code.
Any ideas?

I had a similar problem relating to restoring scroll position. For me the solution was to force the text view to layout before changing the visible rect:
[textView.layoutManager ensureLayoutForTextContainer:textView.textContainer];

Related

How can I position views over a NSTextView such that they have no fixed position, but a sort of gravity to a point?

So I have an NSTextView with text in it. Some of the parts of the text have a NSTextAttachment.Key so I can find their position in the NSTextContainer. I'm adding notes/annotations to the text right now just by hard coding the frame of the annotation to be under the given part of the text. This fails miserably when two annotations are close to each other(they then overlap), or when an annotation's text position is near an edge of the NSTextView(it then gets clipped and cut off).
What I would really like is to set a rule like, this view(the annotation), likes to be close to this given point, but is flexible. This view does not want to overlap other views or go beyond the bounds of the parent view.
The other rule which may be harder is, this view does not want to cover the NSTextView text. Since that text is not a view itself, one solution may be, since I'll know the font size, and the line height, I could calculate that if say the lines of text are at Y positions 100, 200, 300, etc. Then the annotation could only have Y positions of 75, 125, 175, 225, etc.
Some other notes:
This NSTextView is not editable. It is display only and updates based on changes to another NSTextView, so these positions won't need to be dynamic such that a user is adding text in realtime, rather at fixed intervals the editable textview's content is read, the annotations queried from the backend, and then the text is written into the read-only textview's NSTextStorage, and the annotation NSViews are added to the read-only NSTextView.
MacOS/AppKit only. No UIKit
Here is a diagram of the sort of thing I'm aiming to achieve, and was wondering what is the right tool in the AppKit toolbox to try and achieve this? Can something like AutoLayout work with these types of flexible constraints? Or is my only option to rollout something custom that does all of these calculations, and then gives a fixed NSRect to each annotation's NSView?
You can't do it out of the box but there are couple of things you could look into.
Exclusion paths will help you with not covering the actual text view. Whenever you create the note view, just add an exclusion path which could perhaps cover the whole line (meaning a rect of 0, y, fullContainerWidth, lineHeight).
Using NSPopover instead of normal NSViews. They can pop out of the actual window and won't be clipped outside the screen. There is no way to avoid overlapping with them, though.
There are some popover subclasses (such as SFBPopovers) which allow more flexible positioning.
In either case, you will need to do some maths to avoid overlapping. The best way to do this in your case is probably to enumerate the attributes/text attachments. From there, you can figure out which lines will need to have notes displayed, and calculate in advance how much they will take space, and if you'll have to display them both below and above the line.
Note that you will need to do this as you go along, because the layout and
attribute range rects will change as you add exclusions.
If you decide to go with NSViews, you might want to look into creating a specific container NSView subclass for displaying the notes for a single line. It's much easier to handle positioning the notes in local coordinate space, and to figure out how much space they take. You can then display this specific view on top of your NSTextView where needed.

How can I add a UILabel so its text is 2 pixels below the "text" of another UILabel?

I've really searched for an answer to this one but not come up with anything solid so here goes.
I'm dynamically adding UILabels to a view, they could be any size.
I would like the second added one to be placed about 2 pixels below the last regardless whether there is letters that go bellow the line.
I've tried using various method the get the labels frame after it has been sized to font but with no look. The frame always seems to be bigger than the actual text vertically.
Have you guys got any ideas?
First, I'd try setting the label's adjustsFontSizeToFitWidth to NO and see if that it will allow you to mess up the normal proportions, as seems to be your goal.
If that doesn't work, you probably need to draw the string to a CALayer. Create a CALayer and add it to your view's root layer, i.e.:
[yourView.layer addSublayer.yourNewLayerForString].
Set the sublayer's delegate to the class which will do the drawing:
[yourNewLayerForString setDelegate:self];
And implement drawLayer in that class (the one you assigned as delegate). The drawLayer code can be kind of ugly, but you can find examples out there for how to draw a string in a layer. This one looks good: addTextToLayer.
You will then have exact control over the font and string size, and can adjust the layer frame and the view frame to suit your aims.

font size bug with CGContextShowTextAtPoint

I have some rather simple code drawing some text into a CGContext. Here is an excerpt (slightly edited).
CGContextSelectFont(context, "Helvetica", 1.5, kCGEncodingMacRoman);
CGContextShowTextAtPoint(context, xpos, ypos, "Hello", 5);
The text renders ok. For some unknown reason, however, the font changes to a smaller size after I click in the view containing the context. Also when I resize the window containing the view the font returns to original size. What is the reason for this?
1.5 points is mighty tiny to begin with. Assuming no other scaling is in effect, that will be one whole pixel and a blurry pixel above it on the screen.
You're probably seeing a bug that I ran into myself: On entry into drawRect:, the current context's text matrix was not the identity matrix. In my case, I saw it contain a scale by 13 on both axes, plus a translation. (Possibly left over from drawing the title bar.) I filed this in Radar as #10585106, in case you want to file your own and cite it.
The solution is to set the text matrix back to the identity transform before trying to draw text.
Once you do that, you'll find that your text will be exactly as tiny as you asked for it to be. You should change your font size to something more reasonable; Core Text contains a function to get the system fonts (from which you can get their sizes), and AppKit's NSFont class contains methods for the same purpose.

NSTabView with background color

As discussed elsewhere, NSTabView does not have a setBackgroundColor method and subclassing NSTabView and using an drawRect to control it does no longer work - as it does not paint the top 10%, the bit just below the segmented control button.
Now I am a bit surprised by the amounts of work arounds I had to do solving this; see
code: https://github.com/dirkx/CustomizableTabView/blob/master/CustomizableTabView/CustomizableTabView.m
and am wondering if i went down the wrong path. And how to do this better & simpler:
The NSSegmentStyleTexturedSquare seems to yield me a semi-transparent segmented Control. Which means I need to do extra work to hide any bezel lines (line 240, 253).
is there a better way to do this ? I.e. negate its transparency ?
or is there a way I can use the actual/original segmented choise button ?
I find that the colours I need - like the [NSColor windowBackgroundColour] are not set to anything useful (i.e. that one is transparent) -- so right now I hardcode them (lines 87, 94).
Is there a better way to do this ?
I find I need a boatload of fluffy methods to keep things in sync ( line 128, 134, etc).
can this be avoided ?
I find that mimicking the cleverness on rescaling means I need to keep a constant eye on the segemented Control box and remove/resize it. And even then - it is not quite as good as the original
is there a better way to do this than line 157 -- i.e. hear about resizing ? Rather than do it all the time ?
The segementControl fades dark when focus is removed from the window - unlike the real McCoy.
can that easily be prevented ? is there a cheap way to track this ?
Or is this the wrong approach - and should I focus on just a transparent hole here - and let the NSTabViewItem draw a background ? But in any case - then I still have the issue with the Segemented COntrol box - or is there than a way to make that be the default again.
when trying this - I get stuck on the top 20-30 pixels being drawn in the 'real' windows background colour - which is 'transparent' - and hence the colour will not run all the way to the top or behind the segment bar and up to the bezel - but instead stop some 8 pixels below the bottom of the segment controls.
Feedback appreciated - as this feels so far off/suboptimal for such a simple things --
Thanks a lot. Brownie points for hacking/forking the github code :) :) :) As a line of running code says more than a thousand words.
Dw.
PSMTabBarControl is probably the best workaround for you. I have created several custom tab views, but cocoa does not play well with this control. PSMTabBarControl has been updated to support Xcode 4. https://github.com/ciaran/psmtabbarcontrol
Have you tried setting the background color of its underlying CALayer? (Make it a layer-backed view, if it isn't already, by setting wantsLayer = YES.)
If your situation can tolerate some fragility, a very simple and quick approach is to subclass NSTabView and manually adjust the frame of the item subviews. This gives each item a seamless yellow background:
- (void)drawRect:(NSRect)dirtyRect {
static const NSRect offsetRect = (NSRect) { -2, -16, 4, 18 };
NSRect rect = self.contentRect;
rect.origin.x += offsetRect.origin.x;
rect.origin.y += offsetRect.origin.y;
rect.size.width += offsetRect.size.width;
rect.size.height += offsetRect.size.height;
[[NSColor yellowColor] set];
NSRectFill(rect);
[super drawRect:dirtyRect];
}
A future change in the metrics of NSTabView would obviously be a problem so proceed at your own risk!

AdWhirl - moving view to centre screen on rotation

This is probably something quite basic. I am trying to implement AdWhirl into my app, which I have done successfully for the technical part. When I load my app, the add loads and then slides down from the top to sit at the bottom of the screen. However, when I rotate the device, the advert uses "precise" locations and moves off screen. When the advert reloads (refreshes every 15 seconds) the advert moves up to the bottom of the screen of the landscape window. Again, when rotating back from landscape, the Advert Aligns it's self in the middle of the page vertically (covering content) until a new advert loads. I have attached a number of photos, in a series showing what happens, all in order and taken at least 10 seconds apart (showing test advert of "Hello").
My code from the Implementation file is included at the end of this post - sorry for not using the code format, just didn't want to put spaces in front of the whole block, and I think it's all relatively relevant. It's also available at the paste bin: http://pastebin.com/mzavbj2L
Sam
Sorry - it wouldn't let me upload images. Please send me a PM for images.
I recommend handling the rotation in the willRotateToInterfaceOrientation:duration: method or the didRotateFromInterfaceOrientation:method. You will be able to determine what your new orientation is, the new size of your view, and then change the frame of your AdWhirl view to the new location.
After looking a bit closer, however, it looks like you might need to make *adView a variable declared in your .h file so you can access it from the rotation methods.
Once you do that, you can set your new frame as you did in the viewDidLoad: method:
CGSize adSize = [adView actualAdSize];
CGRect newFrame = adView.frame;
newFrame.size = adSize;
newFrame.origin.x = (self.view.bounds.size.width - adSize.width)/ 2;
newFrame.origin.y = (self.view.bounds.size.height - adSize.height);
adView.frame = newFrame;
[UIView commitAnimations];
Ideally, you would move this code into its own method so you can just call it from wherever you want in your view controller code (e.g. viewDidLoad and the rotation function(s)).
Thanks for your help - it was close to a solution (only just got it working tonight - had mostly forgotten about doing it!). After I made you changes to my .h, I was trying to call [adWhirlView adWhirlDidReceiveAd:(AdWhirlView *)adView]. This kept returning errors, even though it was defined in the AdWhirlView class. As a fix, I added -(void) adWhirlDidReceiveAd:(AdWhirlView *)adView and then called each time the frame rotated [self adWhirlDidReceiveAd:(AdWhirlView *)adView];.
Thanks again - so glad it's finally working.
Sam

Resources