I'm programming a cocoa app that presents text (vertically and horizontally) centered on a projector screen.
I'd like to have a small preview of what I see on the "wall" in a cocoa window on the computer's screen (in a NSCollectionView). Just like "presenter mode" in Powerpoint where you see the current and the next slide as previews.
Which way do you recommend for implementing the presented text?
NSTextField turned out to be tricky for aligning and scaling the text. CATextLayer, on the other hand, feels a little bit overkill for such a simple task, even though the scaling works like a charm. Would using NSTextView or drawInRect be a good trade-off?
I appreciate your assistance in making sure that I didn't miss something out before I spend hours and days on this :) My goals are to keep it robust and simple. Thanks!
CATextLayer is definitely not overkill. In fact, a CATextLayer is considerably more lightweight than an NSView and would seem ideal for your situation.
Related
Bear with me on this one, as it's been frustrating me for a few days. This seems like it should be really simple, but for some reason I keep running into a series of frustrating problems. I've done more work on iOS in the past, and never had these kinds of problems that I'm having on OS X. I'm wondering if I'm not understanding something fundamental about the view architecture in AppKit. In particular, I am not very familar with the interaction between layer-backed views and plain views as I have not needed to animate anything on OS X before (and because iOS makes all views layer-backed by default).
Anyway, I'm having some major problems with trying to rotate an NSTextView object by 45 degrees. The text is for some labels that get placed in a larger custom NSView subclass, but most of that seems irrelevant to this specific problem. I'll go through what I've tried and the problems I had. The position of the labels is basically static, so I don't care about whether or not they are animatable or not. The labels need to be created and placed programmatically, not from IB, as their size and position is dependent on dynamically generated data. If it matters, the NSTextViews are using attributed strings.
1) I first did this the way that seemed most straightforward to me, which was simply to set frames for the NSTextView labels, added them as subviews to the graph object, and then called [labelTextView setFrameRotation:45] on them. This worked perfectly on Mountain Lion, but when I tested it on Lion the label height that gets drawn on screen gets increased mysteriously (even though when I log the frame afterwards it is unchanged). That isn't acceptable because I need it to draw a background color for the label so that it looks readable when overlaid onto the graph. So on Lion there is a bug or something in setFrameRotation as applied to NSTextView (or so it would seem). Does anyone know what is going on with that? It's very annoying that it works correctly on 10.8 but not on 10.7. SetFrameCenterRotation has the same problem. Making the view layer-backed, or not, seems to make no difference.
2) I next tried to do this using CATransform3DMakeRotation, after making the parent view and the NSTextView layer-backed. I used the line labelTextView.layer.transform = CATransform3DMakeRotation(M_PI_4, 0, 0, 1.0);, but the text view doesn't rotate when I do this. As a test, I tried the same thing using a plain NSView with a colored background, and it does rotate correctly. Does anyone know possible reasons why this wouldn't work with an NSTextView?
3) I tried enclosing the NSTextView in a NSView container and then calling setFrameRotation on that. This does rotate it correctly, but somehow triggers an infinite recursion in Core Animation that quickly consumes gigabytes of RAM unless I kill the process! Again, any ideas about what's going on there?!?
4) I tried all of this using Autolayout to position the labels AND positioning them manually by setting their frames. No differences in results either way.
5) I could do this using completely custom drawing, but that would be a LOT of work in this particular case. I'd rather not do that just to support 10.7. I also could try NSTextField to see if it suffers from the same thing in part 1), but that would be annoying because I want some of the features of NSTextView (this isn't just a single line blob of text).
What am I doing wrong here? I never imagined something this simple being this difficult to get working correctly on 10.7 + 10.8.
UPDATE:
I tried substituting NSTextField for NSTextView and the problem in 3) goes away (the paragraph styling isn't exactly how I want it yet, but I can possibly fix that). Any ideas on why NSTextView has such problems with being rotated where NSTextField apparently doesn't? That seems insane to me. I'll try NSTextField without the container next to see if it also suffers from the drawing glitch on Lion.
UPDATE 2:
I tried just using an NSTextField instead of an NSTextView and rotating it, and that also worked completely fine on both Lion and Mountain Lion. The visual glitch where the size of the text view changes after rotation also disappears. So I guess the answer is that NSTextView is just totally incapable of having its frame rotated without triggering multiple bugs (especially on older versions of OS X). If that's the case, and NSTextView isn't intended to be used that way, Apple's docs should probably say so. Can anyone confirm that that is the case?
Being a somewhat proficient iOS developer, I have just started working on a desktop OSX project in Cocoa and I'm running into issues that I just can't grasp. So this question is for the OSX developers out there.
I don't like the Interface Builder much, so I tend to write my views in code. The most prominent method I write my view layout code in is a view controller's loadView method, and at least on iOS I use autoresizingMasks for everything. Try out the view small, large, rotated landscape and portrait and if all is dandy, I continue with the next item on my list. Now on the desktop, the autoresizingMask works (or just looks) a little bit different. First of all the properties have different names, but their behavior also seems weird or unexpected.
When I ran into the issue below, I thought it must be my code was wrong, so after trying out long enough I re-created it with Interface Builder just for confirmation's sake, and guess what: I got the exact same result. Take a view with four vertically stacked subviews. Set the middle two to have flexible heights, the outer ones to be fixed. When you run it, size it down and back up again, I get two completely different layouts before and after the resize. See image:
Now I can follow why this happens from a mathematical standpoint between run loops, but from the point of an 'autosizing' or 'autoresizing' feature, this makes absolutely no sense.
Before I try to write the mother-of-all-resizing-topics here, might I ask you these questions? Feel free to elaborate some more on the resizing topic if you feel it adds to the post.
Am I a fool for not wanting to use the Interface Builder on desktop projects?
Should I depend on the autoresizingMask less than I would on iOS projects?
What are decent alternatives to making sure your layout lives up to standards without Interface Builder?
Cheers!
Yes, in my opinion. :)
You should depend on it when it does what you need. When it's insufficient, override resizeSubviewsWithOldSize: and/or resizeWithOldSuperviewSize: (or see below).
???
If you can target 10.7, look at the new constraint-based layout system. Check out the Cocoa Autolayout video from WWDC 2011.
You could also set minSize on your NSWindow to something large enough to prevent the singularity.
I'm not sure I'd say "fool," but refusing to use Interface Builder on the Mac is a very…avante-garde choice.
You should definitely use autosizing on your views.
Be maniacally attentive and spend lots of time making sure everything is right. (This is why I don't recommend going without Interface Builder. In general, what you get is a lot of wasted time that you could have spent doing something else.)
In this case, I think the best approach would be to set a sensible minimum height for the window. Don't let it get too small to display what it needs to display.
So I want to have a view (NSView, NSOpenGLView, something CG related?) which basically displays a map. Such as:
http://dump.tanaris4.com/map.png
Obviously that looks horrible, but I did it using an NSView, and it draws SO slow. Clearly not designed for this.
I just need to allow users to click on the individual (x,y) coordinates to make changes, and zoom into a certain area (to see it better).
Should I go the OpenGL route? And if so - any suggestions as to how to get started? (I was able to follow the guide to draw a triangle, so that's good).
I did find this post on zooming in an NSView: How to implement zoom/scale in a Cocoa AppKit-application
My concern is if I'm drawing over 6000 coordinates and the lines connecting them, this isn't efficient at all.
I don't think using OpenGL would be of any good here. The problem does not seem to be the actual painting, but rather the rendering strategy. You would need a scene graph of some kind to dynamically handle level of detail and culling.
Qt has all this packaged in a nice class class QGraphicsScene (see http://doc.qt.nokia.com/latest/qgraphicsscene.html for reference, and http://doc.qt.nokia.com/main-snapshot/demos-chip.html for an example).
Some basic concepts you should consider using:
http://en.wikipedia.org/wiki/Scene_graph
http://en.wikipedia.org/wiki/Quadtree
http://en.wikipedia.org/wiki/Level_of_detail
Try using core graphics for this, really there is so much that could be done. Watch the video Practical Drawing for iOS Developers from WWDC 2011 and it should give an over view of what can be done with CG.
I believe even CoreGraphics will suffice for what you want to achieve, and that should work under a UIView if you draw the rectangle of your view completely under the DrawRect method of your UIView (you must overload this method). Please see the UIView Class Reference. I have a mobile application that logs points on the UIMapKit, kind of like Nike+, and it certainly works well for massive amounts of points/line segments. There is no reason why this simple approach cannot work for you as well.
I've found a gorgeous switch that I'd like to implement in iOS. The artist (#jasonlong) has kindly shared his PSD of the components at 365psd.com, along with a crafty little javascript as a demo.
Now, here's where I get into trouble... The custom UISlider and UISwitch examples I've found seem to rely on a track that's stationary with a movable knob/toggle. In the switch below, it would require a knob/track to animate behind a mask that also passes through touch events.
I've never been much of an interface coder, but this cute little bugger is just to awesome to leave alone. Can someone point me in the right direction?
I suggest just having the background stationary, and just fade between them? Sliding the track is probably not going to work so well, as you can still see the green/white around the edge of the knob.
I used to use and love RBSplitView, however I failed at reimplementing it programmatically as a certain version of xcode does not support IB plugins anymore.
Therefore I went back to using NSSplitView. NSSplitView is fine for what I need, the thing is that the autoSave of NSSplitView is broken. So I decided to implement it myself.
The thing I am doing at the moment is resizing 1 of the subviews of the NSSplitView.
What is the proper way of resizing an NSSplitView? - setPositionOfDivider:itIndex: should be the way to go ( haven't tried it ), however I do not know how to get the current position of the divider.
-- thanks in advance
In my experience, NSSplitView hates you and wishes you harm. RBSplitView is so much better, it is worth IMO the headache of programatic layout (and I've been so burned with the ShortcutRecorder IB plugins that I will never go back to IB plugins).
That said....
The only way that I know of to determine the current position of the divider is to look at the subviews, find the divider's view, take it's frame, and work out its position keeping in mind the dividerThickness. It is insane that you have to write that code, but the code isn't that incredibly difficult, and you can put it in a category.
Or go back to RBSplitView while you still can, if your needs are ever going to be complicated.
I'm using Swift here but the same method should exist in Objective C:
mySplitter.setPosition(123, ofDividerAtIndex: 0)