How to start an animation without button Tap

Hello:


I've read just about every thing I could find about animations in my MAC OSx app. However, I've not been able to find a useful tutorial of example. Nearly All the examples i found are for iOS and all of those animations are initiated by tapping a button.


I want to be able to make the image on my main window Zoom in from a small icon to fill the window in a gradual zoom in motion as the app main View is loaded.


I will appreciate any example, tutorial, or Swift resource that could be useful in solving this problem.


Thanks in Advance.

Accepted Reply

I am using Xcode version 11.5.

The same as mine. So, the version has nothing to do with your crash.


with the width and height of the ImageView set to 48 and priority 750,

I wrote that you should remove all the constraints except center. Please follow my suggestions when you try my code.


the app still crashes with the error "Unexpectedly found nil while implicitly unwrapping an Optional value: file" on line18

If it happens on line 18. `widthConstraint = imageView.widthAnchor.constraint(equalToConstant: 48)`, then `imageView` is nil.

Maybe the outlet connection to `imageView` is broken in your storyboard. (Sometimes Xcode shows false `connected`-indicator.)

Disconnect and re-connect the IBOutlet to the right NSImageView.


In case I was missing some hidden setup in the storyboard, I made another version with programmatic setup for `imageView`.

Please try this code with NSImageView removed in the storyboard. So that all the constraints are removed and no need to worry about IBOutlet connection.

(Assuming you have the image resource `icon.jpg` and `NSImage(named: "icon.jpg")!` would not cause Unexpectedly found nil.)


import Cocoa

class ViewController: NSViewController {

    var imageView: NSImageView!
    
    var widthConstraint: NSLayoutConstraint!
    var heightConstraint: NSLayoutConstraint!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        imageView = NSImageView(image: NSImage(named: "icon.jpg")!)
        self.view.addSubview(imageView)
        
        imageView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
        imageView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
        imageView.translatesAutoresizingMaskIntoConstraints = false

        widthConstraint = imageView.widthAnchor.constraint(equalToConstant: 48)
        widthConstraint.isActive = true
        heightConstraint = imageView.heightAnchor.constraint(equalToConstant: 48)
        heightConstraint.isActive = true
    }
    
    override func viewWillAppear() {
        super.viewWillAppear()
        
        NSAnimationContext.runAnimationGroup({context in
            context.duration = 1.0
            widthConstraint.animator().constant = self.view.frame.width
            heightConstraint.animator().constant = self.view.frame.height
        }) {
            print("Animation finished")
            //Do some UI updates on animation finished, if you need...
        }
    }

}

Replies

You could try something like this in viewWillAppear to zoom in in 100 steps.

I adapted some code, so look at the idea, some implementaion details may need to be adapted.


if the window is window

imageView is declared somewhere


        let centiemeSeconde = UInt32(5_000) // Go fast 1/200e s in fact
        let fullSize = window.frame.size
        let fullWindowPosition = topRight of the view
    
        var posInMotion = CGPoint.zero  
        var sizeInMotion = CGSize.zero 
        var frameInMotion = CGRect.zero 
        var ratio = CGFloat(0)

            for i in 0...100 {
                usleep(centiemeSeconde)
                ratio = CGFloat(i)
                DispatchQueue.main.async { //  Car c'est une fonction UI ;
                    sizeInMotion = CGSize(width: ratio * fullSize.width / 100 , height: ratio * fullSize.height / 100)
                    posInMotion = CGPoint(x: l (ratio * fullWindowPosition.x/100, y: ratio * (fullWindowPosition.y/100)
                    frameInMotion = NSRect(origin: posInMotion, size: sizeInMotion) 
                    self.imageView.setFrame(SizeframeInMotion)
                }
            }

The key here is the dispatchQueue.

I am having some difficulty getting this code to compile. Below are some changes made to the code with the errors:



overridefunc viewWillAppear() {
        let offsetFromLeft = CGFloat(200)
        let offsetFromBottom = CGFloat(200)
        let centiemeSeconde = UInt32(5_000) // Go fast 1/200e s in fact
        let fullSize = NSWindow(contentRect: NSRect(x:0,y:0, width: 800, height: 500),styleMask: [.titled, .resizable], backing: .buffered, defer: false)
        let fullWindowPosition = self.window.center
   
              var posInMotion = CGPoint.zero
              var sizeInMotion = CGSize.zero
              var frameInMotion = CGRect.zero
              var ratio = CGFloat(0)

                  for i in 0...100 {
                      usleep(centiemeSeconde)
                      ratio = CGFloat(i)
                      DispatchQueue.main.async { //  Car c'est une fonction UI ;

                        sizeInMotion = CGSize(width: ratio * fullSize.width / 100 , height: ratio * fullSize.height / 100) // Value of type 'NSWindow' has no member 'height'
/                         posInMotion = CGPoint(x: 1 (ratio * fullWindowPosition.x/100, y: ratio * (fullWindowPosition.y/100)))
//The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions
Value of type 'NSWindow' has no member 'width'

                         frameInMotion = NSRect(origin: posInMotion, size: sizeInMotion)
                          self.imageView.setFrame(sizeInMotion) //Type 'NSImageView' has no member 'setFrame'
                    }
                  }
            }

Any additional help will be appreciated.

fullSize should be a size, not an NSWindow.


to simplify, you could write:

ratio = CGFloat(i) / 100.0


No need then to have / 100 later


Does it work with those changes ?

Once again I rapidly edited code that was used to resize window, not an ImageView in it. And did not test it yet. So there may be some errors.

Hello Claude31:


I think i've made some progress in coding this thing but can't run it because of compiler complaining that code in lines 19 - 21 is too comples to compile within reasonable time. Is there is a way to break up that code into smaller pieces? I can't see it.



overridefunc viewWillAppear() {
        let offsetFromLeft = CGFloat(200)
        let offsetFromBottom = CGFloat(200)
        let hundredthSecond = UInt32(5_000) // Go fast 1/200e s in fact
        let fullSize = CGRect(x: 0,y: 0,width: self.view.frame.height * 0.9, height: self.view.frame.width * 0.4)
        let fullWindowPosition = self.window.center
           
              var posInMotion = CGPoint.zero
              var sizeInMotion = CGSize.zero
              var frameInMotion = CGRect.zero
              var ratio = CGFloat(0)
       
                  for i in 0...100 {
                      usleep(hundredthSecond)
                      ratio = CGFloat(i)/100
                      DispatchQueue.main.async { //  Car c'est une fonction UI ;
                        sizeInMotion = CGSize(width: ratio * fullSize.width / 100 , height: ratio * fullSize.height / 100)
                      
                        posInMotion = CGPoint(x: 1 (ratio * fullWindowPosition.x/100,
                            y: ratio * (fullWindowPosition.y/100)))
                          frameInMotion = NSRect(origin: posInMotion, size: sizeInMotion)
  //                        self.view.frame(sizeInMotion)
                      }
                  }
            }

It looks to me the example from Claude31 has an error with the parenthesis and you have compounded the error.


I don't code in Swift but I would guess this line:

posInMotion = CGPoint(x: 1 (ratio * fullWindowPosition.x/100,  y: ratio * (fullWindowPosition.y/100)))

should be:


posInMotion = CGPoint(x: 1 (ratio * fullWindowPosition.x/100),  y: ratio * (fullWindowPosition.y/100))

Hi janabanana,


Thanks for the reply. I made the change and added a comma (,) at the end of line 5


With that change I get 2 errors:

1. Consecutive statements on a line must be separated by ';'

2. Expected expression


If I don't use the comma at the end of that line, or add a semicolon I get a bunch of errors.


1. Cannot call value of non-function type 'Int'

2. Consecutive statements on a line must be separated by ';'

3. Expected expression

4. Value of type '(NSWindow) -> () -> Void' has no member 'x'

5. Value of type '(NSWindow) -> () -> Void' has no member 'y'


Where do you think the problem is?


for i in 0...100 {
                      usleep(hundredthSecond)
                      ratio = CGFloat(i)/100
                      DispatchQueue.main.async { //This is a UI Function
                        sizeInMotion = CGSize(width: ratio * fullSize.width / 100 , height: ratio * fullSize.height / 100),
                      
                        posInMotion = CGPoint(x: 1 (ratio * fullWindowPosition.x/100),  y: ratio * (fullWindowPosition.y/100)),
                           
                          frameInMotion = NSRect(origin: posInMotion, size: sizeInMotion)
  //                        self.view.frame(sizeInMotion)
                      }
                  }

For some reason, you have extra commas at end of lines.


Should check carefully the code, like this:


        for i in 0...100 {
            usleep(hundredthSecond)
            ratio = CGFloat(i)/100
            DispatchQueue.main.async { //This is a UI Function
                sizeInMotion = CGSize(width: ratio * fullSize.width , height: ratio * fullSize.height)
                posInMotion = CGPoint(x: ratio * fullWindowPosition.x, y: ratio * fullWindowPosition.y)
                frameInMotion = NSRect(origin: posInMotion, size: sizeInMotion)
            }
        }

Hello Claude31:


Removing the commas generate all the errors in my previous reply. The only error that persists with the commas in place is // "Expected Expression". The three expressions after

sizeInMotion = ...

posInMotion = ...

frameInMotion = ...


generate the same error "Missing Expression" depending on where the parentheses are located.

The code below generates the single error, but calls for a semicolon at the end of line 5 in addition to the comma. This syntax is very difficult to decipher for some reason.





for i in 0...100 {
                      usleep(hundredthSecond)
                      ratio = CGFloat(i)/100
                      DispatchQueue.main.async { //This is a UI Function
                        sizeInMotion = CGSize(width: ratio * fullSize.width / 100, height: ratio * fullSize.height / 100);,
                      
                        posInMotion = CGPoint(x: 1 ratio * fullWindowPosition.x/100,  y: ratio * fullWindowPosition.y/100),
                           
                            frameInMotion = NSRect(origin: posInMotion, size: sizeInMotion),
                         self.view.frame(sizeInMotion)
                      }
                  }

Why don't you simply use the animation feature of macOS?


class ViewController: NSViewController {

    //imageView, its center constrained,
    //width and height are not constrained in the storyboard
    @IBOutlet weak var imageView: NSImageView!
    
    var widthConstraint: NSLayoutConstraint!
    var heightConstraint: NSLayoutConstraint!
    
    override func viewDidLoad() {
        super.viewDidLoad()

        widthConstraint = imageView.widthAnchor.constraint(equalToConstant: 48)
        widthConstraint.isActive = true
        heightConstraint = imageView.heightAnchor.constraint(equalToConstant: 48)
        heightConstraint.isActive = true
        imageView.image = NSImage(named: "room.jpg")
    }
    
    override func viewWillAppear() {
        super.viewWillAppear()
        
        NSAnimationContext.runAnimationGroup({context in
            context.duration = 1.0
            widthConstraint.animator().constant = self.view.frame.width
            heightConstraint.animator().constant = self.view.frame.height
        }) {
            print("Animation finished")
            //Do some UI updates on animation finished, if you need...
        }
    }

}

I'm not sure if this fits for your app, but at least, this compiles and runs without errors.

I still see the comma at the end of line:


sizeInMotion = CGSize(width: ratio * fullSize.width / 100, height: ratio * fullSize.height / 100);,

Hello 00per:

Don't know if you saw my response. It been saying "Currently Being Moderated" for quite a while.


When I run the code the image doesn't show and the debug has a very long set of NSLayoutConstraints problems after "Unable to simultaneously satisfy constraints"

Set the priority of the constraints lower--750 should do it.

Seems you have not set the constraints as I have shown in the comment. Remove all the constraints for the image view and add only constrant for the center of the image view.

Hi Guys,


I am using Xcode version 11.5. I don't know if that has anything to do with the issues i am experiencing but with the width and height of the ImageView set to 48 and priority 750, the app still crashes with the error "Unexpectedly found nil while implicitly unwrapping an Optional value: file" on line18


import Cocoa

class ViewController:


NSViewController {

    @IBOutlet weak var imageView: NSImageView!
   
        //imageView, its center constrained,
        //width and height are not constrained in the storyboard
     var widthConstraint: NSLayoutConstraint!
      var heightConstraint: NSLayoutConstraint!
       
      override func viewDidLoad() {
          super.viewDidLoad()
   
          widthConstraint = imageView.widthAnchor.constraint(equalToConstant: 48)
          widthConstraint.isActive = true
          heightConstraint = imageView.heightAnchor.constraint(equalToConstant: 48)
          heightConstraint.isActive = true
          imageView.image = NSImage(named: "icon.jpg")
      }
       
      override func viewWillAppear() {
          super.viewWillAppear()
           
          NSAnimationContext.runAnimationGroup({context in
              context.duration = 1.0
              widthConstraint.animator().constant = self.view.frame.width
              heightConstraint.animator().constant = self.view.frame.height
          }) {
              print("Animation finished")
              //Do some UI updates on animation finished, if you need...
          }
      }

     
    }
   
var representedObject: Any? {
        didSet {
        // Update the view, if already loaded.
        }
    }