Sliding Animation NSView background color in swift - swift

Trying to modifying the color of NSView with sliding animation like Google Trends
let hexColors = ["56A55B", "4F86EC", "F2BC42", "DA5040"]
#IBAction func changeColor(sender: NSButton) {
let randomIndex = Int(arc4random_uniform(UInt32(hexColors.count)))
NSAnimationContext.runAnimationGroup({_ in
//duration
NSAnimationContext.current.duration = 5.0
self.view.animator().layer?.backgroundColor = NSColor(hex: hexColors[randomIndex]).cgColor
}, completionHandler:{
print("completed")
})
}
I tried using NSAnimationContext to set duration of color change, but it does not work. However it works with the alphaValue of the view.

I'm not sure if you have gotten your answer yet. But this might be able get it to work:
let hexColors = ["56A55B", "4F86EC", "F2BC42", "DA5040"]
#IBAction func changeColor(sender: NSButton) {
let randomIndex = Int(arc4random_uniform(UInt32(hexColors.count)))
NSAnimationContext.runAnimationGroup({ context in
//duration
context.duration = 5.0
// This is the key property that needs to be set
context.allowsImplicitAnimation = true
self.view.animator().layer?.backgroundColor = NSColor(hex: hexColors[randomIndex]).cgColor
}, completionHandler:{
print("completed")
})
}
here is what the documentation says:
/* Determine if animations are enabled or not. Using the -animator proxy will automatically set allowsImplicitAnimation to YES. When YES, other properties can implicitly animate along with the initially changed property. For instance, calling [[view animator] setFrame:frame] will allow subviews to also animate their frame positions. This is only applicable when layer backed on Mac OS 10.8 and later. The default value is NO.
*/
#available(macOS 10.8, *)
open var allowsImplicitAnimation: Bool

Related

Change NSTextField text color with fade animation - Cocoa

I am trying to change NSTextField text color with fade animation by using NSAnimationContext. But it's not working. Please help me to solve this issue. Thanking you all!
Code:
override func viewDidLoad() {
super.viewDidLoad()
label!.animator().textColor = NSColor.black
}
#IBAction func changeColor(_ sender: NSButton){
NSAnimationContext.runAnimationGroup { (context) in
context.duration = 1.0
label!.animator().textColor = NSColor.red
}
}
Here is the outline of one possible solution:
Subclass NSAnimation, say with TextColorAnimation
Have the init take the NSTextField and final NSColor
Override currentProgress as per NSAnimation docs to (a) call the super implementation and (b) set the intermediate color and display the NSTextField
Use NSColor.blend(...) to determine the intermediate color
start this NSAnimation
You should get a nice smooth color transition. HTH
Update the color and alpha value in the completion handler:
#IBAction func changeColor(_ sender: NSButton){
NSAnimationContext.runAnimationGroup({ [self] context in
context.duration = 1.0
context.timingFunction = CAMediaTimingFunction(name: .easeOut)
label.animator().alphaValue = 0.0
label.animator().textColor = NSColor.black
}, completionHandler: { [self] in
NSAnimationContext.runAnimationGroup({ [self] context in
context.duration = 1.0
context.timingFunction = CAMediaTimingFunction(name: .easeIn)
label.animator().alphaValue = 1.0
label.animator().textColor = NSColor.red
}, completionHandler: {
})
})
}

RealityKit - Animate opacity of a ModelEntity?

By setting the color of a material on the model property of a ModelEntity, I can alter the opacity/alpha of an object. But how do you animate this? My goal is to animate objects with full opacity, then have them fade to a set opacity, such as 50%.
With SCNAction.fadeOpacity on a SCNNode in SceneKit, this was particularly easy.
let fade = SCNAction.fadeOpacity(by: 0.5, duration: 0.5)
node.runAction(fade)
An Entity conforms to HasTransform, but that will only allow you to animate scale, position, and orientation. Nothing to do with animation of the material for something like fading it in or out. The effect is in RealityComposer if you create a behavior for animating hide or showing, but there doesn't seem to be something similar to HasTransform to provide functionality for animating opacity.
I've been all around the documentation looking for something, my next idea is essentially creating a custom animation to replace this behavior, but it seems like it should be available and I am just not finding it.
I tested it using different techniques and came to the sad conclusion: you can't animate a material's opacity in RealityKit framework because RealityKit materials don't support animation at runtime (for now I hope). Let's wait for RealityKit's major update.
Here's a code you can use for test
(arView.alpha property just works):
import UIKit
import RealityKit
class ViewController: UIViewController {
#IBOutlet var arView: ARView!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
arView.alpha = 1.0
opacityAnimation()
}
func opacityAnimation() {
UIView.animate(withDuration: 5.0,
animations: {
self.arView.alpha = 0.0
})
}
}
And use this code snippet in order to make sure that animation doesn't work properly
(there's no animation process, just value assignment):
import UIKit
import RealityKit
class ViewController: UIViewController {
#IBOutlet var arView: ARView!
let tetheringAnchor = AnchorEntity(world: [0,0,0])
var material = SimpleMaterial()
let mesh: MeshResource = .generateSphere(radius: 0.5)
var sphereComponent: ModelComponent? = nil
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
material.metallic = .float(1.0)
material.roughness = .float(0.0)
material.baseColor = .color(.red)
sphereComponent = ModelComponent(mesh: mesh,
materials: [material])
tetheringAnchor.components.set(sphereComponent!)
arView.scene.anchors.append(tetheringAnchor)
opacityAnimation()
}
func opacityAnimation() {
UIView.animate(withDuration: 5.0,
animations: {
self.material.metallic = .float(1.0)
self.material.roughness = .float(0.0)
self.material.baseColor = .color(.green)
self.sphereComponent = ModelComponent(mesh: self.mesh,
materials: [self.material])
self.tetheringAnchor.components.set(self.sphereComponent!)
self.arView.scene.anchors.append(self.tetheringAnchor)
})
}
}
As #AndyFedo says there is currently no way to animate the opacity nor alpha of an Entity.
Even changing a SimpleMaterial at run time currently results in flickering.
Having said this I was able to animate the Alpha of a SimpleMaterials Color, however based on testing it is in no way optimal or recommended for that matter.
But just in case you wanted to try to further experiment with this avenue please see an attached example which assumes that you only have a single SimpleMaterial:
class CustomBox: Entity, HasModel, HasAnchoring {
var timer: Timer?
var baseColour: UIColor!
//MARK:- Initialization
/// Initializes The Box With The Desired Colour
/// - Parameter color: UIColor
required init(color: UIColor) {
self.baseColour = color
super.init()
self.components[ModelComponent] = ModelComponent(mesh: .generateBox(size: [0.2, 0.2, 0.2]),
materials: [SimpleMaterial (color: baseColour, isMetallic: false)]
)
}
required init() { super.init() }
//MARK:- Example Fading
/// Fades The Colour Of The Entities Current Material
func fadeOut() {
var alpha: CGFloat = 1.0
timer = Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { timer in
if alpha == 0 {
timer.invalidate()
return
}
var material = SimpleMaterial()
alpha -= 0.01
material.baseColor = MaterialColorParameter.color(self.baseColour.withAlphaComponent(alpha))
material.metallic = .float(Float(alpha))
material.roughness = .float(Float(alpha))
DispatchQueue.main.async {
self.model?.materials = [material]
}
}
}
}
As such just to test you can create and then call the function like so:
let box = CustomBox(color: .green)
box.position = [0,0,-0.5]
arView.scene.anchors.append(box)
box.fadeOut()
Also I would politely ask, that this answer not get downvoted as I am simply iterating the fact that (a) it isn't possible with any current built in methods, and (b) that it can in part be achieved albeit to a very limited extent (and thus currently; in a way which one would see fit for production).
I don't know if it suits with your use case. But you should consider video material.
As you can see in this WWDC session (2min45). An entity with complex pulsating opacity.
https://developer.apple.com/videos/play/wwdc2020/10612/
you can also create the fade in experience in Reality Composer and trigger the .rcproject file in Xcode. Have not tested other interactions with .rcproject but I know at least this can load a model to fade in into the scene.

How to animate NSSlider value change in Cocoa application using Swift

How do I animate NSSlider value change so it looks continuous?
I tried using NSAnimation context
private func moveSlider(videoTime: VLCTime) {
DispatchQueue.main.async { [weak self] in
NSAnimationContext.beginGrouping()
NSAnimationContext.current.duration = 1
self?.playerControls.seekSlider.animator().intValue = videoTime.intValue
NSAnimationContext.endGrouping()
}
}
My NSSlider still does not move smoothly.
To put you into the picture, I am trying to make a video player which uses NSSlider for scrubbing through the movie. That slider should also move as the video goes on. As I said, it does move but I can not get it to move smoothly.
There is an Apple sample code in objective-C for exactly what you are looking for. Below how it would look like in Swift.
Basically you need an extension on NSSlider
extension NSSlider {
override open class func defaultAnimation(forKey key: NSAnimatablePropertyKey) -> Any?{
if key == "floatValue"{
return CABasicAnimation()
}else {
return super.defaultAnimation(forKey: key)
}
}
}
Then you can simply use something like this in your move slider function.
private func moveSlider(videoTime: Float) {
NSAnimationContext.beginGrouping()
NSAnimationContext.current.duration = 0.5
slider.animator().floatValue = videoTime
NSAnimationContext.endGrouping()
}

NSWindow transition animation for View Controller Segues

I've been trying to figure out how to get a NSWindow to perform a transition animation with Swift 3. I found a few examples in Objective-C, but I haven't been able to tease out the relevant details and translate into the target language / newer SDK and get it applied to the right object. This one is pretty flipping cool, but it's ~8yrs old: http://www.theregister.co.uk/2009/08/21/cocoa_graphics_framework/ -- I would imagine there's a better way to do the CGSCube effect now in macOS Sierra with Swift.
Here's what I have so far:
class ViewController: NSViewController {
func doAnimation() {
if let layer = view .layer {
layer.backgroundColor = CGColor.black
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = 0.0
rotateAnimation.toValue = CGFloat(CGFloat.pi * 2.0)
rotateAnimation.duration = 10.0
layer.add(rotateAnimation, forKey: nil)
}
}
override func viewWillAppear() {
if let window = self.view.window {
window.styleMask = NSWindowStyleMask.borderless
window.backingType = NSBackingStoreType.buffered
window.backgroundColor = NSColor.clear
window.isOpaque = false
window.hasShadow = false
}
}
override func viewDidLoad() {
super.viewDidLoad()
doAnimation()
}
}
This doesn't really do the trick at all. I get a white background on my window instead of a transparent background, and my view rolls across the frame instead of the window frame itself being animated.
Ideally, I would like to do something more advanced like these 3d transitions -- https://cocoapods.org/pods/MKCubeController & https://www.appcoda.com/custom-view-controller-transitions-tutorial/ & https://github.com/andresbrun/ABCustomUINavigationController#cube but I'm not quite sure how to translate the examples from the iOS SDK over to the macOS SDK without UIKit. (Annoyingly, I remember pulling this off a few years back in ObjC, but the project was lost somewhere between formats / new computers.)
How can I apply a transform to the NSWindow itself while segueing between View Controllers? Any tips toward adding some 3d to this effect would be appreciated. I hoping there's maybe a CocoaPod that gets me halfway there.

How to set the BlurRadius of UIBlurEffectStyle.Light

I was wondering how to set the radius/blur factor of iOS new UIBlurEffectStyle.Light? I could not find anything in the documentation. But I want it to look similar to the classic UIImage+ImageEffects.h blur effect.
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let blur = UIBlurEffect(style: UIBlurEffectStyle.Light)
let effectView = UIVisualEffectView(effect: blur)
effectView.frame = frame
addSubview(effectView)
}
Changing alpha is not a perfect solution. It does not affect blur intensity. You can setup an animation from nil to target blur effect and manually set time offset to get desired blur intensity. Unfortunately iOS will reset the animation offset when app returns from background.
Thankfully there is a simple solution that works on iOS >= 10. You can use UIViewPropertyAnimator. I didn't notice any issues with using it. I keeps custom blur intensity when app returns from background. Here is how you can implement it:
class CustomIntensityVisualEffectView: UIVisualEffectView {
/// Create visual effect view with given effect and its intensity
///
/// - Parameters:
/// - effect: visual effect, eg UIBlurEffect(style: .dark)
/// - intensity: custom intensity from 0.0 (no effect) to 1.0 (full effect) using linear scale
init(effect: UIVisualEffect, intensity: CGFloat) {
super.init(effect: nil)
animator = UIViewPropertyAnimator(duration: 1, curve: .linear) { [unowned self] in self.effect = effect }
animator.fractionComplete = intensity
}
required init?(coder aDecoder: NSCoder) {
fatalError()
}
// MARK: Private
private var animator: UIViewPropertyAnimator!
}
I also created a gist: https://gist.github.com/darrarski/29a2a4515508e385c90b3ffe6f975df7
You can change the alpha of the UIVisualEffectView that you add your blur effect to.
let blurEffect = UIBlurEffect(style: UIBlurEffectStyle.Light)
let blurEffectView = UIVisualEffectView(effect: blurEffect)
blurEffectView.alpha = 0.5
blurEffectView.frame = self.view.bounds
self.view.addSubview(blurEffectView)
This is not a true solution, as it doesn't actually change the radius of the blur, but I have found that it gets the job done with very little work.
Although it is a hack and probably it won't be accepted in the app store, it is still possible. You have to subclass the UIBlurEffect like this:
#import <objc/runtime.h>
#interface UIBlurEffect (Protected)
#property (nonatomic, readonly) id effectSettings;
#end
#interface MyBlurEffect : UIBlurEffect
#end
#implementation MyBlurEffect
+ (instancetype)effectWithStyle:(UIBlurEffectStyle)style
{
id result = [super effectWithStyle:style];
object_setClass(result, self);
return result;
}
- (id)effectSettings
{
id settings = [super effectSettings];
[settings setValue:#50 forKey:#"blurRadius"];
return settings;
}
- (id)copyWithZone:(NSZone*)zone
{
id result = [super copyWithZone:zone];
object_setClass(result, [self class]);
return result;
}
#end
Here blur radius is set to 50. You can change 50 to any value you need.
Then just use MyBlurEffect class instead of UIBlurEffect when creating your effect for UIVisualEffectView.
Recently developed Bluuur library to dynamically change blur radius of UIVisualEffectsView without usage any of private APIs: https://github.com/ML-Works/Bluuur
It uses paused animation of setting effect to achieve changing radius of blur. Solution based on this gist: https://gist.github.com/n00neimp0rtant/27829d87118d984232a4
And the main idea is:
// Freeze animation
blurView.layer.speed = 0;
blurView.effect = nil;
[UIView animateWithDuration:1.0 animations:^{
blurView.effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
}];
// Set animation progress from 0 to 1
blurView.layer.timeOffset = 0.5;
UPDATE:
Apple introduced UIViewPropertyAnimator class in iOS 10. Thats what we need exactly to animate .effect property of UIVisualEffectsView. Hope community will be able to back-port this functionality to previous iOS version.
This is totally doable. Use CIFilter in CoreImage module to customize blur radius. In fact, you can even achieve a blur effect with continuous varying (aka gradient) blur radius (https://stackoverflow.com/a/51603339/3808183)
import CoreImage
let ciContext = CIContext(options: nil)
guard let inputImage = CIImage(image: yourUIImage),
let mask = CIFilter(name: "CIGaussianBlur") else { return }
mask.setValue(inputImage, forKey: kCIInputImageKey)
mask.setValue(10, forKey: kCIInputRadiusKey) // Set your blur radius here
guard let output = mask.outputImage,
let cgImage = ciContext.createCGImage(output, from: inputImage.extent) else { return }
outUIImage = UIImage(cgImage: cgImage)
I'm afraid there's no such api currently. According to Apple's way of doing things, new functionality was always brought with restricts, and capabilities will bring out gradually. Maybe that will be possible on iOS 9 or maybe 10...
I have ultimate solution for this question:
fileprivate final class UIVisualEffectViewInterface {
func setIntensity(effectView: UIVisualEffectView, intensity: CGFloat){
let effect = effectView.effect
effectView.effect = nil
animator = UIViewPropertyAnimator(duration: 1, curve: .linear) { [weak effectView] in effectView?.effect = effect }
animator.fractionComplete = intensity
}
private var animator: UIViewPropertyAnimator! }
extension UIVisualEffectView{
private var key: UnsafeRawPointer? { UnsafeRawPointer(bitPattern: 16) }
private var interface: UIVisualEffectViewInterface{
if let key = key, let visualEffectViewInterface = objc_getAssociatedObject(self, key) as? UIVisualEffectViewInterface{
return visualEffectViewInterface
}
let visualEffectViewInterface = UIVisualEffectViewInterface()
if let key = key{
objc_setAssociatedObject(self, key, visualEffectViewInterface, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
return visualEffectViewInterface
}
func intensity(_ value: CGFloat){
interface.setIntensity(effectView: self, intensity: value)
}}
This idea hits me after tried the above solutions, a little hacky but I got it working. Since we cannot modify the default radius which is set as "50", we can just enlarge it and scale it back down.
previewView.snp.makeConstraints { (make) in
make.centerX.centerY.equalTo(self.view)
make.width.height.equalTo(self.view).multipliedBy(4)
}
previewBlur.snp.makeConstraints { (make) in
make.edges.equalTo(previewView)
}
And then,
previewView.transform = CGAffineTransform(scaleX: 0.25, y: 0.25)
previewBlur.transform = CGAffineTransform(scaleX: 0.25, y: 0.25)
I got a 12.5 blur radius. Hope this will help :-)
Currently I didn't find any solution.
By the way you can add a little hack in order to let blur mask less "blurry", in this way:
let blurView = .. // here create blur view as usually
if let blurSubviews = self.blurView?.subviews {
for subview in blurSubviews {
if let filterView = NSClassFromString("_UIVisualEffectFilterView") {
if subview.isKindOfClass(filterView) {
subview.hidden = true
}
}
}
}
for iOS 11.*
in viewDidLoad()
let blurEffect = UIBlurEffect(style: .dark)
let blurEffectView = UIVisualEffectView()
view.addSubview(blurEffectView)
//always fill the view
blurEffectView.frame = self.view.bounds
blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
UIView.animate(withDuration: 1) {
blurEffectView.effect = blurEffect
}
blurEffectView.pauseAnimation(delay: 0.5)
There is an undocumented way to do this. Not necessarily recommended, as it may get your app rejected by Apple. But it does work.
if let blurEffectType = NSClassFromString("_UICustomBlurEffect") as? UIBlurEffect.Type {
let blurEffectInstance = blurEffectType.init()
// set any value you want here. 40 is quite blurred
blurEffectInstance.setValue(40, forKey: "blurRadius")
let effectView: UIVisualEffectView = UIVisualEffectView(effect: blurEffectInstance)
// Now you have your blurred visual effect view
}
This works for me.
I put UIVisualEffectView in an UIView before add to my view.
I make this function to use easier. You can use this function to make blur any area in your view.
func addBlurArea(area: CGRect) {
let effect = UIBlurEffect(style: UIBlurEffectStyle.Dark)
let blurView = UIVisualEffectView(effect: effect)
blurView.frame = CGRect(x: 0, y: 0, width: area.width, height: area.height)
let container = UIView(frame: area)
container.alpha = 0.8
container.addSubview(blurView)
self.view.insertSubview(container, atIndex: 1)
}
For example, you can make blur all of your view by calling:
addBlurArea(self.view.frame)
You can change Dark to your desired blur style and 0.8 to your desired alpha value
If you want to accomplish the same behaviour as iOS spotlight search you just need to change the alpha value of the UIVisualEffectView (tested on iOS9 simulator)