iOS - passing information between subviews within a single view controller - model-view-controller

In my drawing app I have a switch called "Snap to grid" -- When its state is changed I need drawing in my canvas view to honor that change. At present, the switch is a subview of a UIView so that the text label and the switch can travel together during a device orientation change.
I'm trying to decide what is the best way of communicating the state change. My thought train has been comparing this to using jQuery selector, like a $("#switch") -- in essence, when the view controller has instantiated the subviews (and subviews of those subviews), how do you get a pointer to the specific subview you're looking for?
I should add that my views are created in the Interface Builder portion of Xcode, so they are loaded from a .nib file rather than me creating them (which obviously would give me a chance to hold on to a pointer to the subviews I need).

Related

How is a subview of an NSView dynamically positioned?

I am attempting to change the coordinates of an NSButton that is contained within a parent NSView and something is clearly not working, because the button position does not change. Both elements are defined in a nib file and the parent view has animation applied to it using CoreAnimation.
I have tried the following.
button.frame.origin.x = 500
and...
var frame:CGRect = button.frame
frame.origin.x = 500
button.frame = frame
Even with the animations disabled, I can not seem to dynamically position the subview. Is there some feature that prevents children views from being positioned programmatically?
Please note that I am using Swift with XCode 6.3.1.
I'm guessing you're using AutoLayout constraints, given you're using the latest tools.
If so, setting a subview's frame directly won't work the way you're expecting (if it does anything at all, it'll cause strange drawing glitches / flashing when mixed with animation). You have to create outlets for your layout constraints and modify them.
If you're not using AutoLayout, I suggest having a look at your button outlet to make sure it's actually connected (ie, you're not talking to nil). Even if the outlet is connected, make sure it's not nil at runtime - you may be trying to talk to the button before the nib is loaded and the outlet / action connections are restored.

Move all subviews to container view keeping positions and constraints

I'm using Xcode 5 and autolayout in my project. I have a .xib file with a lot of subviews and constraints between them. Now I need to create an intermediate fullscreen container view and put all subviews there. So now I have view->subviews, and I want view->container view->subviews. When I do this in IB by dragging subviews into container view, they all get centred and all constraints get lost. It's going to be a little hard to restore all constraints manually. Is there a smart way to do this keeping all subviews' positions and constraints?
Add your container view to the hierarchy (to get an object ID) in Interface Builder and close the view. Edit the .xib file manually, in a text editor, and move all subviews to be under the container view. Constraints are created in IB with references to IB object IDs, so you will have to replace the constraints from referencing the superview with the container; a quick replace run should be sufficient.
You can do this in Xcode 6 by using cut&paste to a separate view (which you can later drag around to wherever you actually need it).

Why might an NSTableView redraw every cell on scroll?

I have an NSTableView with 5 columns, each containing a stock NSTableCellView in the nib. (The stock cells have a text box and an optional image.) When populated, the table has around 50 rows. Everything displays fine, but scrolling performance is pretty bad. It looks like this is happening because every cell gets a drawRect: message for its full rect whenever the table scrolls. However, neither reloadData nor reloadDataForRowIndexes:ColumnIndexes: is getting called, so it's not that. It's not the contents of the cells, either: I tried commenting out all my code to just leave the default cell image and text for each cell, and performance is the same. While scrolling, none of the cells get updated. (I put a breakpoint in tableView:viewForTableColumn:row: to make sure.)
My implementation has the following delegate methods:
tableView:viewForTableColumn:row: in the delegate; this creates and populates new cells via makeViewWithIdentifier:owner:
numberOfRowsInTableView: in the data source; this returns a constant number
tableView:sortDescriptorsDidChange: in the data source
That's it! Not very complicated, and yet.
I feel like I'm missing something completely obvious. What could be causing these redraws?
EDIT: Come to think of it, several other applications (uTorrent, Xcode) seem to exhibit the same slow scrolling behavior. You can really see it if you look at CPU usage while scrolling. On the other hand, Activity Monitor has buttery-smooth scrolling that barely spikes the CPU at all. How do I get that in my app?
EDIT 2: I think I found my mistake. According to Apple:
In iOS apps, Core Animation is always enabled and every view is backed
by a layer. In OS X, apps must explicitly enable Core Animation
support by doing the following:
Link against the QuartzCore framework. (iOS apps must link against this framework only if they use Core Animation interfaces explicitly.)
Enable layer support for one or more of your NSView objects by doing one of the following:
In your nib files, use the View Effects inspector to enable layer support for your views. The inspector displays checkboxes for
the selected view and its subviews. It is recommended that you enable
layer support in the content view of your window whenever possible.
For views you create programmatically, call the view’s setWantsLayer: method and pass a value of YES to indicate that the
view should use layers.
Enabling layer support in one of the preceding ways creates a
layer-backed view. With a layer-backed view, the system takes
responsibility for creating the underlying layer object and for
keeping that layer updated. In OS X, it is also possible to create a
layer-hosting view, whereby your app actually creates and manages the
underlying layer object. (You cannot create layer-hosting views in
iOS.) For more information on how to create a layer-hosting view, see
“Layer Hosting Lets You Change the Layer Object in OS X.”
I'll add an answer as soon as I fix my performance issues. With a cursory pass, my scrolling is still bumpy, but my CPU usage has dropped from 70% to 10% while scrolling.
For the record... Edit 2 by the OP makes the world of difference.
In iOS apps, Core Animation is always enabled and every view is backed
by a layer. In OS X, apps must explicitly enable Core Animation
support by doing the following:
Link against the QuartzCore framework. (iOS apps must link against
this framework only if they use Core Animation interfaces explicitly.)
Enable layer support for one or more of your NSView objects by doing
one of the following:
In your nib files, use the View Effects inspector to enable layer
support for your views. The inspector displays checkboxes for the
selected view and its subviews. It is recommended that you enable
layer support in the content view of your window whenever possible.
For views you create programmatically, call the view’s setWantsLayer:
method and pass a value of YES to indicate that the view should use
layers. Enabling layer support in one of the preceding ways creates a
layer-backed view. With a layer-backed view, the system takes
responsibility for creating the underlying layer object and for
keeping that layer updated. In OS X, it is also possible to create a
layer-hosting view, whereby your app actually creates and manages the
underlying layer object. (You cannot create layer-hosting views in
iOS.) For more information on how to create a layer-hosting view, see
“Layer Hosting Lets You Change the Layer Object in OS X.”

How to "stick" a UIScrollView subview to top/bottom when scrolling?

You see this in iPhone apps like Gilt. The user scrolls a view, and a subview apparently "sticks" to one edges as the rest of the scrollView slides underneath. That is, there is a text box (or whatever) in the scrollView, that as the scrollView hits the top of the view, then "sticks" there as the rest of the view continues to slide.
So, there are several issues. First, one can determine via "scrollViewDidScroll:" (during normal scrolling) when the view of interest is passing (or re-appearing). There is a fair amount of granularity here - the differences between delegate calls can be a hundred of points or more. That said, when you see the view approach the top of the scrollView, you turn on a second copy of the view statically displayed under the scrollView top. I have not coded this, but it seems like it will lack a real "stick" look - the view will first disappear then reappear.
Second, if one does a setContentOffset:animated, one does not get the delegate messages (Gilt does not do this). So, how do you get the callbacks in this case? Do you use KVO on "scroll.layer.presentationLayer.bounds" ?
Well, I found one way to do this. When the user scrolls by flicking and dragging, the UIScrollView gives its delegate a "scrollViewDidScroll:" message. You can look then to see if the scroller has moved the content to where you need to take some action.
When "sticking" the view, remove it from the scrollView, and then add it to the scrollView's superview (with an origin of 0,0). When unsticking, do the converse.
If you use the UIScrollView setContentOffset:animated:, it gets trickier. What I did was to subclass UIScrollView, use a flag to specify it was setContentOffset moving the offset, then start a fast running timer to monitor contentOffset.
I put the method that handles the math and sticking/unsticking the child view into this subclass. It looks pretty good.
Gilt uses a table view to accomplish this. Specifically, in the table view's delegate, these two methods:
– tableView:viewForHeaderInSection:
and – tableView:heightForHeaderInSection:

Validating a drag to an NSCollectionView isn't reflected visually

I have an NSCollectionView that I want to accept items dragged from elsewhere in my application.
I implement collectionView:validateDrop:proposedIndex:dropOperation: and collectionView:acceptDrop:index:dropOperation: in the collectionview's delegate and register for the appropriate dragged types. Both methods get called fine when I drag the appropriate types, but I don't get a blue focus ring over the collectionview indicating a valid drag.
Have tried both the collection view and its containing scroll view on Default and External settings for the focus ring. Both are just the standard non-derived Cocoa classes. Wondered if there was anything else I should try. Surely it isn't necessary to subclass NSCollectionView for this?
Thanks
Chris
Focus rings are not typically the correct way to provide feedback about drag destinations. Every view does it slightly differently. NSTextView shows the insertion bar. NSTableView shows a blue line in between rows for Before drop operations, and shows a bezel around the row for On drop operations. (See NSTableViewDropOperation)
NSCollectionView shows a "gap" between existing subviews to show where the items will be dropped for Before drop operations, and it will set the selected property on NSCollectionViewItem to YES for On drop operations. (Note: NSCollectionViewItem doesn't do anything by default to visibly represent the selected property. You must implement that yourself.)
Since NSCollectionView's feedback uses existing subviews only, it appears there isn't any feedback at all for empty NSCollectionView's. You would need to subclass to provide this behavior yourself. You could also file a bug to request that NSCollectionView do this itself.

Resources