I'm trying to create a view hierarchy similar to what you'd expect in a media playback viewer like the QuickTime Player:
+ Host View
+ Video Controls (NSView layer-backed)
+ Video View (NSView layer-hosted)
+ AVPlayerLayer
Since layer-hosted views cannot contain subviews, the video controls view is a sibling of the video view and simply ordered-front so that it's on top of the video view.
This current view hierarchy appears to be working fine for me, but I remain a bit confused over whether it's officially "supported" because of the overlapping, sibling views (the video controls view always overlaps the video view).
This Stack Overflow question: Is there a proper way to handle overlapping NSView siblings? offers conflicting information regarding overlapping sibling views.
I would assume the more 'correct' way to handle this is for the video controls to be a subview of the video view, which is only possible of I change the video view from being a layer-hosted view to a layer-backed view.
By default a layer-backed view uses a basic CALayer as its backing store, but NSView exposes makeBackingLayer to allow you to return a custom layer such as an AVPlayerLayer.
By doing that, and moving the controls view to be a subview of this layer-backed video view, things also appear to work properly but now there's an AVPlayer object that is directly modifying the contents of the AVPlayerLayer. That seems to be contrary to the requirement that in a layer-backed view, you should never modify the contents of the layer without going through the NSView using something like drawRect or updateLayer.
This seems to leave me with two choices, neither of which appears 'correct' based on my interpretation of the document:
Option 1:
Layer-hosted view for the AVPlayerLayer
Overlapping sibling view for the controls view.
Option 2:
Layer-back view with an AVPlayerLayer via makeBackingLayer
AVPlayer that directly modifies the contents of the AVPlayerLayer
Controls view as a subview of the video view
I'm inclined to think that option #2 is the more correct way and that in this scenario, it's OK for the AVPlayer to directly modify the contents of the AVPlayerLayer even though it's in a layer-backed view, but I'm not certain and would be curious if others have any thoughts or experiences with a setup like this.
Apple has some old (ancient in computer terms, 2007!) code that doesn't even compile in Xcode 6 without some tweaks. It shows some controls that are overlaying a QuickTime movie layer. Download it here: https://developer.apple.com/library/mac/samplecode/CoreAnimationQuickTimeLayer/Introduction/Intro.html .
It's hard to say that just because they provided source code that it's considered a best practice, but I would suggest you just build it the way you think is best. This is not one of those areas so heavily developed on that a best practice is likely to exist. For me personally it makes the most sense to use overlapping sibling views as to ensure that you don't mess with the video rendering. Whether that's the right way or not is probably somewhat subjective. You can maybe access one of the old quicktime developers mailing lists or even ask on the Apple developer forums. At the end of the day though you should probably just stick with the method that makes the most sense to you since you're likely to be the one to maintain or build upon it in the future.
Related
I have a Mac app that needs to be based on multiple modules. That is, a single window with multiple views, and the default view with a menu. That menu should open one module on the default window and then if I select another module, the contents of the window should change with another view. Those views also have different states, so I made multiple views for each module.
In a nutshell, my app is a single AppDelegate.h/.m, a single xib file, with one NSWindow object and multiple NSView views. Those views have different states, so I load different other related NSViews.
To load a view, I use [window setContentView:viewNameView]; which I know that causes the old NSView to lose state, so I need to keep them all in memory for each module.
Is this the right approach?
Thank you!
You don't describe how and where you want the menu but a widely used method is to have a sourcelist on the left and the content on the right. You see this everywhere including Apples own apps.
If you create a sourcelist on the left of your window and place an NSBox on the right side.
Set up the sourcelist (NSOutlineView) to react to - outlineViewSelectionDidChange: which is an NSOutlineView delegate method.
Here you can check the identifier on the selected item in the menu and set the content view for the NSBox accordingly with - setContentView:
Here's a great introduction to using NSOutlineViews for anyone interested.
Edit: Depending on how many views you have it might be easier to have an NSTabView (in tabless mode) and just switch tabs in the - outlineViewSelectionDidChange: method. This is also widely used and the user won't see the difference.
You will want to look up NSWindowController for managing your window and xib, and NSViewController for managing views. The app delegate shouldn't do much (in fact you probably could remove the header file and merge it with .m).
Some references to look at:
https://www.mikeash.com/pyblog/friday-qa-2013-04-05-windows-and-window-controllers.html
https://developer.apple.com/library/mac/samplecode/ViewController/Introduction/Intro.html
Yes that will work. What you may end up needing as well, is a custom Navigation Controller. Unfortunately Cocoa doesn't have an NSNavigationController, so you'll have to write something on your own. But basically yeah what you'll do is swap out the contentView with the next view you want to display-- and keep a stack of views you've navigated to so you can support going back (or you could use a dictionary to add transition keys to create strongly linked transitions)
Here's an good example somebody posted in a previous thread-- if you just search for Cocoa Mac Navigation Controller you should find some helpful results :)
Mac OS X Cocoa multiview application navigation
Another thing that you may want to keep in mind, which came up for me, is if your views are of different sizes. If they are, and you are using auto-layout, you will need to update the constraints to resize the window appropriately as views are swapped out
What's the best method to implement swiping between views in OS X? These views are initialized from nibs with their view controllers and they're the same class type. To give some background, each view displays relevant data for the current date.
I'd like to create a function that loads the data for the next (or previous) date. I could simply load the data into the current view, but can I do that with an animation that's similar to swiping between spaces in OS X? I imagine I'd have to initialize a new view, load the data there, and then initiate the swiping transition to the new view.
I'm worried that the performance of creating all these new views would be pretty bad. Here are some options I've considered to address this:
Create a dictionary of NSDate to MYViewController. Load and store each view from this hashmap, but this could take a lot of memory.
Create a doubly-linked list of MYViewController and load/store sequentially dated views. This could potentially take a lot of memory also and if the user jumps to a date, the caching would just be erased.
Any thoughts? If there's some way to load the data in the current view, I feel like that'd be the best option.
Thanks!
Have you considered just simply using UIScrollView and having a single UIViewController to manage all the views? Here's a good tutorial from Ray Wenderlich's site on UIScrollView :
http://www.raywenderlich.com/10518/how-to-use-uiscrollview-to-scroll-and-zoom-content
Alternatively, have you considered possibly using horizontal table view? Here's a third party implementation, which I've seen recommended on other SO posts:
https://github.com/TheVole/HorizontalTable
Edit
Sorry, I quickly read your question and assumed it was for iOS...
I think you might be looking for something like NSCollectionView perhaps (not sure if this would support offscreen views in a horizontal manner well or not...?)
Here's the docs on it:
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/NSCollectionView_Class/Introduction/Introduction.html
Here's a tutorial on it:
http://andrehoffmann.wordpress.com/2009/08/29/nscollectionview-tutorial-for-dummies-xcode-3-1-3/
(Honestly haven't done much OSX development, so a bit out of my area of expertise here... I wish you luck though!)
Cocoa view transition animation is not an easy thing. This is much more complicated coding than view controller management.
You need to decide how to achieve the desired effects. Take a look this document and other references: Animation Programming Guide for Cocoa
I did similar work a few months ago. My final resolution was using single view and show animation effects only with graphics APIs (image drawing) because it was more simpler than Core Animation.
I have a NSTableView that holds a name to all of my NSImageView's, and depending on the order that the NSImageViews were added, the last one would be in the front.
But in the case that I want the user to be able to bring a NSImageViews in front of another, how would I do that?
Thanks in advance.
As explained on the NSView reference page, the z-order of a view's subview is given by their oder in the view's subviews array. You can insert a new subview relative to the others using -addSubview:positioned:relativeTo:, and you can reorder the existing subviews by getting the subviews property, reordering the views therein as you like, and then calling -setSubviews: to set the new order.
Specifically, the docs say:
The order of the subviews may be considered as being back-to-front,
but this does not imply invalidation and drawing behavior.
What you are asking about, I think, is how to control the z-order of views.
For historical performance reasons this is not well supported in AppKit (unlike UIKit in iOS), since until somewhat recently you couldn't actually have sibling views that overlap.
A common approach to this (on recent OS X releases) is to use Core Animation (in particular, CALayer) which does support z-ordering natively, but this is probably overkill for what you need (and in any event is going to have a learning curve for you).
What are you actually trying to do? Are these images (image views) precisely on top of one another? If so, the easiest (and much better performing) approach is to have a single NSImageView and to just send -setImage:... to it to change the displayed image.
I'd love to see a detailed explanation on how to manage views programmatically. I'll provide an overview of how I'm doing it now and would like either comments on how my approach sucks or just an overview of how to do it cleanly and properly.
Basically, in my app's main view controller's loadView method, I first create a root view and set self.view to it. When I want to attach a controller, say, the first one that displays, I call this method:
-(void) attachViewForController:(UIViewController*)controller
{
[self.mRootView addSubview:controller.view];
[controller viewWillAppear:NO];
}
Notice that I explicitly call viewWillAppear (I believe it wasn't automatically calling it), where I manually perform any animations to bring the view in (sliding in, fading in, etc). Is there anything wrong or strange with this approach here?
Now, when I want to leave this view and switch to another, I call a method to setup the switch:
-(void) setControllerSwitch:(UIViewController*)outgoingController
incomingController:(UIViewController*)incomingController
delay:(float)delay;
{
self.mOutgoingController = outgoingController;
self.mIncomingController = incomingController;
self.mSwitchControllerTimer = [NSTimer scheduledTimerWithTimeInterval:delay target:self selector:#selector(switchControllerCallback) userInfo:nil repeats:NO];
}
At this moment in time, I've begun the exit animations for the outgoing view, and this method records the controllers and schedules a method that will perform the actual switch at the moment the outgoing view is done animating. Like this:
-(void) switchControllerCallback
{
self.mSwitchControllerTimer = nil;
// remove outgoing view
[mOutgoingController.view removeFromSuperview];
// add incoming view
[self attachViewForController:mIncomingController];
}
Is this a decent way to manage things? A few points:
I know I could probably instead setup a callback to trigger when the outgoing controllers animations end, but chose to just do it via an explicit delay param to leave room to allow me to cross fade views. However, I think calling setControllerSwitch early may in fact not allow crossfading because it would junk the old controller early and make it chop off its animation.
As mentioned earlier, I'm curious to know if explicitly calling viewWillAppear is a no no and there is a more appropriate way to manage view flow.
Copied from Apple developer documentation but it help me lot to understand about views and maneging multiple views.
Tips for Using Views Effectively
Custom views are useful for situations where you need to draw something the standard system views do not provide, but it is your responsibility to ensure that the performance of your views is good enough. UIKit does everything it can to optimize view-related behaviors and help you achieve good performance in your custom views. However, you can help UIKit in this aspect by considering the following tips.
Views Do Not Always Have a Corresponding View Controller
There is rarely a one-to-one relationship between individual views and view controllers in your application. The job of a view controller is to manage a view hierarchy, which often consists of more than one view used to implement some self-contained feature. For iPhone applications, each view hierarchy typically fills the entire screen, although for iPad applications a view hierarchy may fill only part of the screen.
As you design your application’s user interface, it is important to consider the role that view controllers will play. View controllers provide a lot of important behaviors, such as coordinating the presentation of views on the screen, coordinating the removal of those views from the screen, releasing memory in response to low-memory warnings, and rotating views in response to interface orientation changes. Circumventing these behaviors could cause your application to behave incorrectly or in unexpected ways.
For more information view controllers and their role in applications, see View Controller Programming Guide for iOS.
Minimize Custom Drawing
Although custom drawing is necessary at times, it is also something you should avoid whenever possible. The only time you should truly do any custom drawing is when the existing system view classes do not provide the appearance or capabilities that you need. Any time your content can be assembled with a combination of existing views, your best bet is to combine those view objects into a custom view hierarchy.
Take Advantage of Content Modes
Content modes minimize the amount of time spent redrawing your views. By default, views use the UIViewContentModeScaleToFill content mode, which scales the view’s existing contents to fit the view’s frame rectangle. You can change this mode as needed to adjust your content differently, but you should avoid using the UIViewContentModeRedraw content mode if you can. Regardless of which content mode is in effect, you can always force your view to redraw its contents by calling setNeedsDisplay or setNeedsDisplayInRect:.
Declare Views as Opaque Whenever Possible
UIKit uses the opaque property of each view to determine whether the view can optimize compositing operations. Setting the value of this property to YES for a custom view tells UIKit that it does not need to render any content behind your view. Less rendering can lead to increased performance for your drawing code and is generally encouraged. Of course, if you set the opaque property to YES, your view must fills its bounds rectangle completely with fully opaque content.
Adjust Your View’s Drawing Behavior When Scrolling
Scrolling can incur numerous view updates in a short amount of time. If your view’s drawing code is not tuned appropriately, scrolling performance for your view could be sluggish. Rather than trying to ensure that your view’s content is pristine at all times, consider changing your view’s behavior when a scrolling operation begins. For example, you can reduce the quality of your rendered content temporarily or change the content mode while a scroll is in progress. When scrolling stops, you can then return your view to its previous state and update the contents as needed.
Do Not Customize Controls by Embedding Subviews
Although it is technically possible to add subviews to the standard system controls—objects that inherit from UIControl—you should never customize them in this way. Controls that support customizations do so through explicit and well-documented interfaces in the control class itself. For example, the UIButton class contains methods for setting the title and background images for the button. Using the defined customization points means that your code will always work correctly. Circumventing these methods, by embedding a custom image view or label inside the button, might cause your application to behave incorrectly now or at some point in the future if the button’s implementation changes.
I'm looking for some kind of a basic, straightforward example of how to work with a pair of NSScrollers in an NSScrollView with a custom NSView.
There are sporadic examples out there, largely consisting of contrived examples using programatically created interfaces, or based on the assumption that the developer is working with a typical ImageView or TextView. Even the Sketch example is based on an NSView that uses the Page Setup in the Print dialog for the bounds, and everything is managed by Cocoa. So there's no real discussion or examples anywhere of how make it all work using a custom Model (though that may be part of the problem, because what does one base the Model on?). Even Apple's own documentation is dodgy here.
Essentially, I have a sub-classed NSView enbedded in an NSScrollView (per the Scoll View Guide), that a user can click in the view to create, edit and delete objects, much like an illustration program. The Model is those objects that are just data wrappers that simply record their position for drawRect: to use. The height and width are based on custom values that are being translated into pixels as needed.
My problem is that all of the examples I have found are based on either a text editor, an image viewer, or uses the standard document sizes in the Page Setup dialog. Because these are common document types, Cocoa basically manages for the developer, so the interaction code is more or less hidden (or I'm just not seeing it for what it is). My project doesn't fit any of those needs, and I have no need for printing. Thrusting my Model into the documentView property wouldn't work.
I'm just looking for a simple example on how to initialize the NSScrollers with a custom, object-oriented Model (the documentView), and handle scrolling and updating based on user action, such as when the user drags a smattering of objects off to the left or down or the window gets resized. I think I'm close to getting it all together, but I'm missing the jumping off point that ties the controls to document.
(Not that it matters in a Cocoa question, but when I did this in REALbasic, I would simply calculate and apply the MaxX, MaxY to a ScrollBar's Maximum value based on user actions, watch the position in the ScrollBar when the user clicks, and draw as needed. NSScrollers in the NSScrollView context aren't nearly as obvious to me, it seems.)
I appreciate the time taken by everyone, but I'm updating with more information in the hopes of getting an answer I can use. I'm sorry, but none of this is making sense, Apple's documents are obtuse, but perhaps I'm missing something painfully obvious here...
I have an array of objects sitting in a subclassed NSDocument which are data holders that tell drawRect what and where to draw. This is straight from the Sketch example. The Sketch example uses the document sizes in the Page Setup dialog for the size, so there's nothing to show here. I'm cool with Cocoa handling the state of the scroll bars, but how do I link up the ScrollView to see the initial editor's state held in the NSDocument and updates to those objects and the editor? Do I calculate my own NSRect and pass that to the NSScrollView? Where and how? Am I doing this in my custom NSView which has been embedded in the NSScrollView or my NSDocument in init? The NSScrollView isn't created programmatically (there's no easy way of doing that), so it's all sitting in Interface Builder waiting to be hooked up. I'm missing the hook up bit.
Perhaps I'm wearing my "I don't get it" cap this week, but this can't be this difficult. Illustration apps, MIDI Editors, and countless other similar custom apps do this all the time.
SOLVED (mostly):
I think I have this sorted out now, though it's probably not the best implementation.
My document class now has a NSRect DocumentRect property that looks at all of its objects and gives back a new NSRect based on their location. I call it in my subclassed NSView's mouse events with
[self setFrame:[[self EditorDocument] DocumentRect]];
This updates the size of the View based on user interaction, and the window now handles the scrolling where it didn't before. At this point I'm figuring out how to get the frame to expand while dragging, but at least I now have the fundamental concept I was missing.
The answer given pointed me in the direction I needed to go here (documentView requiring a view, which translated to looking at the NSView class), so Peter gets the credit. Thanks so much for help.
The document view isn't a model, it's a view. That's why it's called the document view.
The reason there are so few examples on working with NSScrollers directly is because you normally don't. You work with NSScrollView and let it handle the scrollers for you.
All you need to do is make a view big enough to show the entire model, then set that as the document view of the scroll view. From there, it should Just Work. You don't need to manage any of the scrolling-related numbers yourself; Cocoa handles them for you.
For details, see the Scroll View Programming Guide.