Is it possible to control the duration and timing function of a NSViewController transition? - core-animation

I'm using NSViewController's transition(from:to:options:completionHandler:). It is working beautifully, but now I am wanting more control (like a custom timing function).
Is this possible? Or would it require another approach to transitioning between view controllers?

Yes, you can modify the animation context if you capture it in an animation group:
NSAnimationContext.runAnimationGroup({ context in
context.duration = 3
parentController.transition(from: childA, to: childB: options: yourOptions, completionHandler: yourCompletionHandler)
})
Unfortunately, it seems, modifying the context's timingFunction property doesn't do anything.
For OS X 10.11 and older, you may use this shim:
https://gist.github.com/BenLeggiero/63f84d5f7fd8c9b3c9501d1b68110983

Related

Set AutoLayout Size Class Programmatically?

With iOS 8 and Xcode 6, in storyboards we now have the screen size grid letting us select a size class. Where you can select layout formatting for the different screen sizes.
I have found this brilliantly helpful, as it allows me to set the base constraints and then unique ones for each screen size.
My question is, can you do this programmatically? I create my NSLayoutConstraint as normal but I need to be able to specify different constraints for different screen sizes.
iOS 8 introduces the active property on NSLayoutConstraint. It allows you to activate or deactivate a constraint. There are also methods to activate/deactivate multiple constraints.
+ (void)activateConstraints:(NSArray *)constraints
+ (void)deactivateConstraints:(NSArray *)constraints
Keep your constraints in arrays when creating them programmatically.
Create an array for each of the layouts you need.
Activate/Deactivate whatever set of constraints you need from within willTransitionToTraitCollection
To answer your question, you can set the size class programmatically, however, it's a bit of a pain. You must call "setOverrideTraitCollection" but from the parent view controller, not the one you actually wished to make a trait change to.
In my situation, I wanted to change the Master VC of a split view controller on iPad to look differently than the one on the iPhone, however, they are both set to Compact width / Regular height by default. So I subclassed the Master's nav controller and added code to set the Master's traits to Regular width when it's not an iPhone.
Swift code:
class MasterNavigationController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
if (self.traitCollection.userInterfaceIdiom != .Phone) {
let newTraitCollection = UITraitCollection(horizontalSizeClass: .Regular)
self.setOverrideTraitCollection(newTraitCollection, forChildViewController: self.topViewController)
}
}
}
I hope this helps someone looking for a similar solution.
It's a bit confusing & hard to find in the documentation because a "size class" isn't actually a "Class" like NSObject. They're really defined in an enum/typedef called: UIUserInterfaceSizeClass
The way to get the horizontal & vertical size class for a view is with a UITraitCollection
Class/Type methods for UITraitCollection allow you to create one based on a particular display scale (e.g. retina or not), from an array of other trait collections, with a UI idiom (iPad/iPhone), or specific horizontal & vertical options (compact, regular), but to be honest I'm not sure yet how you'd use this...
This question discusses updating constraints when the traitCollection changes, using willTransitionToTraitCollection(newCollection: UITraitCollection!,
withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator!)
You're right that both the UITraitCollection and its properties are readonly, but clearly you can create a new collection for some reason, and handle layout changes when the traitCollection changes.
This previous question is pretty similar & links to an Apple article about using Adaptive Layout. Also check the WWDC video "Building Adaptive Apps with UIKit."

Titanium - Reset zoom scale in webview

Is it possible to programmatically reset the zoom/scale that the user has done in a webview? My issue is when the webview is pinch zoomed in landscape and you rotate the phone, the webview is still zoomed in and in my case, portrait mode is now extremely zoomed in.
I've tried webview.repaint() but that seems to do absolutely nothing. I know I can call webiew.reload() but that just uses more bandwidth for my server as well as the users data plan so I want to avoid that.
I'm using Titanium Studio, build: 3.1.3.xxx, 3.1.3 GA Ti SDK and compiling for iOS 7 SDK.
Based on this thread and the fact that there was no way to easily achieve this, I have created a module. It extends the Ti.UI.WebView with two methods, setZoomLevel() and scrollToTop(). It is very basic at the moment, but I'm planning on extending its functionality gradually.
https://github.com/mwfire/titanium-module-extended-webview
Have a look, it's an alternative to modifying the core code.
Did you give a try at setting the scalesPageToFit property of your webview upon orientation change?
http://docs.appcelerator.com/titanium/3.0/#!/api/Titanium.UI.WebView-property-scalesPageToFit
I found the answer which was a bit more than I wanted to do. I had to actually modify the Titanium SDK obj-c files.
I modified 3 files:
TiUIWebView.h
-(void)resetZoomScale; //-- added this line to define the function in the header file
TiUIWebView.m
//-- Added this function that will actually handle the resize
- (void)resetZoomScale
{
[webview.scrollView setZoomScale:1.0]; //-- reset the scroll view back to 1
}
TiUIWebViewProxy.m
//-- Call the resetZoomScale function in TiUIWebView.m file
//-- I believe this also exposes the function to javascript
-(void)resetZoomScale:()args
{
TiThreadPerformOnMainThread(^{[(TiUIWebView*)[self view] resetZoomScale];}, NO);
}
I can now call myWebView.resetZoomScale(); and whatever pinch/zooming has been done on the web view will be reset back none or 1

Starting transitions before activate resolves

Is there a simple way to use Durandal's transitions to fade out an old view immediately, and fade in the new one once its activate function has resolved?
Here's some context:
I have a Durandal 1.2 SPA where most of the views' activate functions make service calls and return a promise as appropriate. This all works great, but transitions only run when loading is complete sometimes services can take few seconds to respond - leading to a poor user experience where you click on a link and it takes a few seconds before anything noticeable happens.
The solution is to animate away the old view immediately (before activate resolves) and then animate in the new view when activate is done. At the moment, every animated view corresponds to a route and I don't expect this to change. I've come up with and tested a few solutions that all work but seem less than ideal (and don't leverage Durandal's transition framework):
Manually animate away every view in its deactivate() and animate it back in via its viewAttached()
Bind the .page-host div's visibility to router.isNavigating (using a custom binding to handle the transition such as the fadeVisible example from the knockout site)
Manually subscribe to router.isNavigating and run custom logic when it changes
I've tried them all and so far I like the custom binding on the .page-host div the best since it involves the least amount of code, but it only works in my case because of my specific circumstances and isn't a generic solution.
This seems like exactly the sort of thing Durandal's transitions were created for. Is there a more elegant way to do this in Durandal using transitions (in 1.2 or the upcoming 2.0 release)?
I saw this question which seems to be asking something similar, but seems a little less specific and doesn't have a relevant answer.
Move your async logic out of activate and put it into viewAttached. This will allow the view to animate in immediately. Then, from viewAttached, you are safe to execute async code without breaking KO because the bindings have already been applied. (Note: viewAttached is renamed in 2.0 to attachedToParent.)
You can add handler for 'router:route:activating' event by adding next code into your shell.js:
router.on('router:route:activating').then(function () {
//fade out animation goes here;
//Usually that is changing of observable which will add/remove css class for your view (or shows loading overlay)
//Animation of view transition could be done by add class css3 animation.
});
And in case if you are using "loading" observable - in custom transition you can set this observable back to hide loading overlay:
if (context.bindingContext.$data && context.bindingContext.$data.isLoading && ko.isObservable(context.bindingContext.$data.isLoading )) {
composition.current.complete(function() {
context.bindingContext.$data.isLoading (true);
});
}

CABasicAnimation and custom types

I'm not very familiar with CoreAnimation, so I hope I've just missed something pretty simple. I want to animate a custom property (NSGradient) of a NSView in a simple manner, with [[view animator] setGradient:gradient];. I defined + (id)defaultAnimationForKey:(NSString *)key and returned a simple CABasicAnimation, however, no animation is executed. Since this works for simpler types and NSColor, I guess CABasicAnimation doesn't work with gradients. Fine, but in this particular case gradients are trivial (two stops, always), so I can easily write an interpolation functions. The question: how can I define a custom interpolation? I googled around regarding delegates on view, layer and animations, subclassing animation class etc., but I wasn't able to figure the things out. Thanks!
I thought I remembered passing by some Apple documentation when I was learning how to use Core Animation that showed how to set up animations that couldn't be handled by properticode describedes that are supplied with defined animations. Along the way I stumbled across some sample code from Apple that is described as:
A single gradient layer is displayed and continuously animated using new random colors.
That may be the answer to the specific task you already handled another way. I found it in the Documentation and API Reference within Xcode and the name of the sample code is simply Gradients. (Note that there is an original version 1.0 and an updated version 1.1 that was redone this year in April and so should be easier to use with current tools.
But, the larger question of creating a custom animation that can't be automated by Core Animation itself is to follow the example from Apple's Animation Programming Guide for Cocoa in the section Using an NSAnimation Object. It's described under the topic Subclassing NSAnimation and the recommended method is shown under the heading Smooth Animations. You override the setCurrentProgress: method so that each time it is called you first invoke Super so that NSAnimation updates the progress value, i.e., your custom animated property and then do any updating or drawing needed for the next frame of your animation. Here are the notes and example code provided by Apple in the referenced documentation:
As mentioned in “Setting and Handling Progress Marks,” you can attach a series of progress marks to an NSAnimation object and have the delegate implement the animation:didReachProgressMark: method to redraw an object at each progress mark. However, this is not the best way to animate an object. Unless you set a large number of progress marks (30 per second or more), the animation is probably going to appear jerky.
A better approach is to subclass NSAnimation and override the setCurrentProgress: method, as illustrated in Listing 4. The NSAnimation object invokes this method after each frame to change the progress value. By intercepting this message, you can perform any redrawing or updating you need for that frame. If you do override this method, be sure to invoke the implementation of super so that it can update the current progress.
Listing 4 Overriding the setCurrentProgress: method
- (void)setCurrentProgress:(NSAnimationProgress)progress
{
// Call super to update the progress value.
[super setCurrentProgress:progress];
// Update the window position.
NSRect theWinFrame = [[NSApp mainWindow] frame];
NSRect theScreenFrame = [[NSScreen mainScreen] visibleFrame];
theWinFrame.origin.x = progress *
(theScreenFrame.size.width - theWinFrame.size.width);
[[NSApp mainWindow] setFrame:theWinFrame display:YES animate:YES];
}
So basically you define a "progress value" (possibly composed of several values) that defines the state of your custom animation and write code that given the current "progress value" draws or changes what is drawn when the animation is at that particular state. Then you let NSAnimation run the animation using the normal methods of setting up an animation and it will execute your code to draw each frame of the animation at the appropriate time.
I hope that answers what you wanted to know. I doubt I could have found this easily by searching without having seen it before since I finally had to go to where I thought it might be and skim page by page through the entire topic to find it again!

How to activate a custom screensaver preview in Cocoa/Obj-C?

I have created a fairly simple screensaver that runs on Mac OS 10.6.5 without issue.
The configuration screen has accumulated quite a few different options and I'm trying to implement my own preview on the configureSheet window so the user (just me, currently) can immediately see the effect of a change without having to OK and Test each change.
I've added an NSView to the configureSheet and set the custom class in Interface Builder to my ScreenSaverView subclass. I know that drawRect: is firing, because I can remove the condition for clearing the view to black, and my custom preview no longer appears with the black background.
Here is that function (based on several fine tutorials on the Internet):
- (void)drawRect:(NSRect)rect
{
if ( shouldDrawBackground )
{
[super drawRect:rect];
shouldDrawBackground = NO;
}
if (pausing == NO)
[spiroForm drawForm];
}
The spiroForm class simply draws itself into the ScreenSaverView frame using NSBezierPath and, as mentioned, is not problematical for the actual screensaver or the built-in System Preferences preview. The custom preview (configureView) frame is passed into the init method for, um, itself (since its custom class is my ScreenSaverView subclass.) The -initWithFrame method is called in configureSheet before returning the configureSheet object to the OS:
[configureView initWithFrame:[configureView bounds] isPreview:YES];
Maybe I don't have to do that? It was just something I tried to see if it was required for drawing.
I eventually added a delegate to the configureSheet to try triggering the startAnimation and stopAnimation functions of my preview via windowWillBeginSheet and windowWillEndSheet notifications, but those don't appear to be getting called for some reason. The delegate is declared as NSObject <NSWindowDelegate> and I set the delegate in the configureSheet method before returning the configureSheet object.
I've been working on this for days, but haven't been able to find anything about how the OS manages the ScreenSaverView objects (which I think is what I'm trying to emulate by running my own copy.)
Does anybody have any suggestions on how to manage this or if Apple documents it somewhere that I haven't found? This isn't really required for the screensaver to work, I just think it would be fun (I also looked for a way to use the OS preview, but it's blocked while the configureSheet is activated.)
OK, there are a couple of 'duh' moments involved with the solution:
First of all, I was setting the delegate for the sheet notifications to the sheet itself. The window that the sheet belongs to gets the notifications.
Secondly, that very window that the sheet belongs to is owned by System Preferences, I don't see any way to set my delegate class as a delegate to that window, so the whole delegate thing doesn't appear to be a viable solution.
I ended up subclassing NSWindow for the configureSheet so that I could start and stop animation on my preview by over-riding the makeKeyWindow and close methods.
- (void) makeKeyWindow
{
if (myPreview != nil)
if ( ! [myPreview isAnimating])
{
[myPreview startAnimation];
}
[super makeKeyWindow];
}
I also had to add an IBOutlet for my preview object itself and connect it in Interface Builder.
Still working out a couple of issues, but now when I click on my screensaver Options button, my configureSheet drops down and displays its own preview while you set options. Sheesh. The hoops I jump through for these little niceties. Anyway, I like it. Onward and upward.