Animate meteor traveling from top to bottom in Swift 3 - swift

I want an image of a red circle to move from the top of the screen to the bottom, as though it were a meteor traveling across the screen. It does move, but it moves in a millisecond with no animation. How do I make it move smoothly like a meteor with a tail?
class ViewController: UIViewController {
let meteorImage = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.meteorImage.backgroundColor = .clear
self.meteorImage.frame = CGRect(x: 50, y: 0, width: 50, height: 50)
self.meteorImage.isHidden = false
self.meteorImage.image = #imageLiteral(resourceName: "success")
view.addSubview(self.meteorImage)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func buttonPressed(_ sender: UIButton) {
let totalHeight = Int(view.frame.height)
var currentHeight = 0
while currentHeight < totalHeight {
self.meteorImage.backgroundColor = .clear
self.meteorImage.frame = CGRect(x: 50, y: currentHeight, width: 50, height: 50)
self.meteorImage.isHidden = false
self.meteorImage.image = #imageLiteral(resourceName: "success")
view.addSubview(self.meteorImage)
currentHeight += 1
}
}
}

Animations have to be done with an animate call.
This is a simple way to do an animation.
UIView.animate(withDuration: 0.1, animations: {
// Apply Changes to frame.
})
Consider also: instead of doing the while loop simply set the final location of the meteor's frame and set the duration to the amount of time you want it to take.
let newFrame= CGRect(x: 50,
y: currentHeight,
width: 50,
height: 50)
UIView.animate(withDuration: 3.0, animations: {
// Apply Changes to frame.
self.meteorImage.frame = newFrame
})
If you want to make your animation more complicated you can look into keyframe animations on a UIView.

Related

Programmatically Setting AutoresizingMask to NSView Object

I wonder why it always fails whenever I try to set the autoresizingMask to an NSView object programmatically? In the following lines of code, I have a tiny, blue NSVIew object (topLeftView) set at the top-left corner of its parent container.
class ViewController: NSViewController {
// MARK: - IBOutlet
#IBOutlet weak var panView: PanView!
override func viewDidLoad() {
super.viewDidLoad()
makeImageContainer(point: CGPoint.zero)
}
func makeViewContainer(point: CGPoint) {
let myView = NSView(frame: CGRect(origin: CGPoint.zero, size: CGSize(width: 150.0, height: 150.0))
myView.wantsLayer = true
if let myLayer = myView.layer {
myLayer.backgroundColor = NSColor.orange.cgColor
}
panView.addSubview(myView) // panView is an IBOutlet-connected `NSView` object.
/* tiny, blue `NSView` object */
let topLeftView = NSView(frame: CGRect(origin: CGPoint(x: 0.0, y: 150.0 - 6.0), size: CGSize(width: 6.0, height: 6.0)))
topLeftView.wantsLayer = true
topLeftView.autoresizingMask = [.minXMargin, .maxYMargin]
if let layer = topLeftView.layer {
layer.backgroundColor = NSColor.blue.cgColor
}
myView.addSubview(topLeftView)
}
}
So it has minXMargin and maxYMargin autoresizingMasks.
Now, I want to click on a push button to resize this orange NSView container object to see if the tiny, blue NSView object will stick at the top-left corner.
#IBAction func testClicked(_ sender: NSButton) {
for i in 0..<panView.subviews.count {
let subView = panView.subviews[i]
if i == 4 {
subView.setFrameSize(CGSize(width: 200.0, height: 200.0))
}
}
}
And the tiny guy has moved by 50 points to the right and 50 points to the bottom. What am I doing wrong? Thanks.

change position UiButton with Animation Swift

i have a button in ViewController and i changed button position when clicked on that .
the question it is how to can move button to new position when Clicked with animation ?
this is view Controller :
override func viewDidLoad() {
super.viewDidLoad()
let X = 50
let Y = 100
let WIDTH = 100
let HEIGHT = 100
movable_Button.frame = CGRect(x: X, y: Y, width: WIDTH , height: HEIGHT)
}
this is change position method When clicked :
#IBAction func movable_Button_DidTouch(_ sender: Any) {
movable_Button.frame = CGRect(x: X + 150, y: Y + 150, width: 100, height: 100)
}
Let's use UIView animate block
#IBAction func movableButtonDidTouch(_ sender: Any) {
// Set initial frame here
UIView.animate(withDuration: 0.35) {
// Set final frame here
}
}
Please notice to remove underline (_) in function name, it's not the convention of Swift, use camel case instead.
Update
class ViewController: UIViewController {
var positions = [CGPoint]()
let width = 100.0
let height = 50.0
var index = 0
lazy var button: UIButton = {
let theButton = UIButton(type: .system)
theButton.frame = CGRect(x: 50, y: 50, width: self.width, height: self.height)
theButton.backgroundColor = .red
return theButton
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.button)
for index in 1...4 {
let point = CGPoint(x: 50 * index, y: 50 * index)
self.positions.append(point)
}
}
#IBAction func changeButtonPosition(_ sender: AnyObject) {
UIView.animate(withDuration: 0.35) { [weak self] in
guard let self = self else { return }
self.index += 1
let point = self.positions[self.index % 4]
self.button.frame = CGRect(origin: point, size: CGSize(width: self.width, height: self.height))
}
}
}

Drag a CGRect using UIPanGestureRecognizer

I am subclassing UIView, and am trying to "drag" a CGRect back and forth across the screen. Basically I want to move(redraw) the rectangle every time I drag my finger. So far, I have this code:
var rectangle: CGRect {
get {
return CGRect(x: 200,
y: 200,
width: frame.width / 6,
height: 15)
}
set {}
}
override func draw(_ rect: CGRect) {
let gesture = UIPanGestureRecognizer(target: self, action: #selector(dragRectangle(recognizer:)))
addGestureRecognizer(gesture)
drawRectangle(rect)
}
func drawRectangle(_ rect: CGRect) {
let path = UIBezierPath(rect: rectangle)
UIColor.black.set()
path.fill()
}
#objc func dragRectangle(recognizer: UIPanGestureRecognizer) {
let translation = recognizer.translation(in: self)
rectangle = CGRect(x: rectangle.midX + translation.x, y: rectangle.midY + translation.y, width: rectangle.width, height: rectangle.height)
setNeedsDisplay()
recognizer.setTranslation(CGPoint.zero, in: self)
}
This is my first time using UIPanGestureRecognizer, so I'm not sure of all the details that go into this. I have set breakpoints in drawRectangle and confirmed that this is being called. However, the rectangle on the screen does not move at all, no matter how many times I try to drag it. What's wrong?
This is how you can do it easily. just copy paste and run this.
//
// RootController.swift
// SampleApp
//
// Created by Chanaka Caldera on 24/6/19.
// Copyright © 2019 homeapps. All rights reserved.
//
import UIKit
class RootController: UIViewController {
private var draggableView: UIView!
private var pangesture: UIPanGestureRecognizer!
override func viewDidLoad() {
super.viewDidLoad()
setdragview()
}
}
extension RootController {
fileprivate func setdragview() {
draggableView = UIView()
draggableView.translatesAutoresizingMaskIntoConstraints = false
draggableView.backgroundColor = .lightGray
view.addSubview(draggableView)
let draggableviewConstraints = [draggableView.leftAnchor.constraint(equalTo: view.leftAnchor,constant: 10),
draggableView.topAnchor.constraint(equalTo: view.topAnchor, constant: 64),
draggableView.widthAnchor.constraint(equalToConstant: 100),
draggableView.heightAnchor.constraint(equalToConstant: 40)]
NSLayoutConstraint.activate(draggableviewConstraints)
pangesture = UIPanGestureRecognizer()
draggableView.isUserInteractionEnabled = true
draggableView.addGestureRecognizer(pangesture)
pangesture.addTarget(self, action: #selector(draggableFunction(_:)))
}
}
extension RootController {
#objc fileprivate func draggableFunction(_ sender: UIPanGestureRecognizer) {
view.bringSubviewToFront(draggableView)
let translation = sender.translation(in: self.view)
draggableView.center = CGPoint(x: draggableView.center.x + translation.x, y: draggableView.center.y + translation.y)
sender.setTranslation(CGPoint.zero, in: self.view)
print("drag works : \(translation.x)")
}
}
here is the demo,
Hope this will help. cheers !
Try like this (check comments through code):
#IBDesignable
class Rectangle: UIView {
#IBInspectable var color: UIColor = .clear {
didSet { backgroundColor = color }
}
// draw your view using the background color
override func draw(_ rect: CGRect) {
backgroundColor?.set()
UIBezierPath(rect: rect).fill()
}
// add the gesture recognizer to your view
override func didMoveToSuperview() {
addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(pan)))
}
// your gesture selector
#objc func pan(_ gesture: UIPanGestureRecognizer) {
// update your view frame origin
frame.origin += gesture.translation(in: self)
// reset the gesture translation
gesture.setTranslation(.zero, in: self)
}
}
extension CGPoint {
static func +=(lhs: inout CGPoint, rhs: CGPoint) {
lhs.x += rhs.x
lhs.y += rhs.y
}
}
To draw rectangles on your view when panning you can do as follow:
import UIKit
class ViewController: UIViewController {
var rectangles: [Rectangle] = []
override func viewDidLoad() {
super.viewDidLoad()
view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(pan)))
}
#objc func pan(_ gesture: UIPanGestureRecognizer) {
switch gesture.state {
case .began:
let rectangle = Rectangle(frame: .init(origin: gesture.location(in: view), size: .init(width: 0, height: 0)))
rectangle.fillColor = .red
rectangle.strokeColor = .white
rectangle.lineWidth = 3
view.addSubview(rectangle)
rectangles.append(rectangle)
case .changed:
let distance = gesture.translation(in: view)
let index = rectangles.index(before: rectangles.endIndex)
let frame = rectangles[index].frame
rectangles[index].frame = .init(origin: frame.origin, size: .init(width: frame.width + distance.x, height: frame.height + distance.y))
rectangles[index].setNeedsDisplay()
gesture.setTranslation(.zero, in: view)
case .ended:
break
default:
break
}
}
}
Sample Project
I figured out most of the problem as to why the rectangle wasn't moving. It turns out that I misunderstood how variable getters and setters work in Swift. Nevertheless, I changed this code:
var rectangle: CGRect {
get {
return CGRect(x: 200,
y: 200,
width: frame.width / 6,
height: 15)
}
set {}
}
to be a lazy variable:
lazy var rectangle: CGRect = {
return CGRect(x: 200,
y: 200,
width: self.frame.width / 6,
height: 15)
}()
The reason I needed get and set in the first place was because I use frame in my variable calculations, and that was not available until the view itself was fully instantiated. I also tweaked this code a bit:
rectangle = CGRect(x: rectangle.midX + translation.x, y: rectangle.midY + translation.y, width: rectangle.width, height: rectangle.height)
and used minX instead of midX:
rectangle = CGRect(x: rectangle.minX + translation.x, y: rectangle.minY + translation.y, width: rectangle.width, height: rectangle.height)
This is because CGRect is initialized with the x and y parameters being the minX and minY.
This is good progress(at least the rectangle moves). However, I am not able to figure out why the rectangle only switches places after I have released my mouse, resulting in choppy movement.

Animation to scale and move UIView (swift)

I have a UIView, placed in the middle of the screen. When the user presses a button, I want it to move up near top of the screen as it shrinks to about a fifth of its size.
I have tried this:
UIView.animateWithDuration(0.7) { () -> Void in
self.main.transform = CGAffineTransformMakeScale(0.2, 0.2)
self.main.transform = CGAffineTransformMakeTranslation(0, -250)
}
But for some reason, that only scales the view. I have also tried to put this in the animateWithDuration:
self.main.frame = CGRectMake(self.view.frame.width/2 - 10, 50, 50, 50)
How can I get both animations to work?
Joe's answer above does exactly as his GIF describes but it doesn't really answer your question since it translates then scales the view (as opposed to both translating and scaling at the same time). Your issue is that you're setting the view's transform in your animation block then immediately overwritting that value with another transform. To achieve both translation and scale at the same time, you'll want something like this:
#IBAction func animateButton(_ sender: UIButton) {
let originalTransform = self.main.transform
let scaledTransform = originalTransform.scaledBy(x: 0.2, y: 0.2)
let scaledAndTranslatedTransform = scaledTransform.translatedBy(x: 0.0, y: -250.0)
UIView.animate(withDuration: 0.7, animations: {
self.main.transform = scaledAndTranslatedTransform
})
}
You can achieve basic UIView animation in several ways in Swift. Below code is just a simple approach...
class ViewController: UIViewController {
#IBOutlet weak var animationButton: UIButton!
#IBOutlet weak var myView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// I added seperate colour to UIView when animation move from one animation block to another.So, You can get better understanding how the sequence of animation works.
self.myView.backgroundColor = .red
}
#IBAction func animateButton(_ sender: UIButton) {
UIView.animate(withDuration: 0.5, delay: 0.0, options: UIViewAnimationOptions.curveEaseIn, animations: {
//Frame Option 1:
self.myView.frame = CGRect(x: self.myView.frame.origin.x, y: 20, width: self.myView.frame.width, height: self.myView.frame.height)
//Frame Option 2:
//self.myView.center = CGPoint(x: self.view.frame.width / 2, y: self.view.frame.height / 4)
self.myView.backgroundColor = .blue
},completion: { finish in
UIView.animate(withDuration: 1, delay: 0.25,options: UIViewAnimationOptions.curveEaseOut,animations: {
self.myView.backgroundColor = .orange
self.myView.transform = CGAffineTransform(scaleX: 0.25, y: 0.25)
self.animationButton.isEnabled = false // If you want to restrict the button not to repeat animation..You can enable by setting into true
},completion: nil)})
}
}
Output:

Display image 5 second after hidden

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Set the image view in the starting location
moveobj.frame = CGRect(x: 100, y: 100, width: /* some width */,
height: /* some height */)
UIView.animate(withDuration: /* some duration */, animations: {
// Move image view to new location
self.moveobj.frame = CGRect(x: 300, y: 300, width: self.moveobj.frame.width, height: self.moveobj.frame.height)
}) { (finished) in
// Animation completed; hide the image view
self.moveobj.isHidden = true
}
}
After animation is completed the image is hidden but I would like to display again this after 5 second in original position. How do I this?
Well all you'd have to do is chain a new animation to the finish that does the opposite of the previous animation (move back to the original location and unhide) - Courtesy of #Rob's comment.
It'd just be:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Set the image view in the starting location
let originalFrame = CGRect(x: 100, y: 100, width: /* some width */,
height: /* some height */)
moveobj.frame = originalFrame
UIView.animate(withDuration: /* some duration */, animations: {
// Move image view to new location
self.moveobj.frame = CGRect(x: 300, y: 300, width: self.moveobj.frame.width, height: self.moveobj.frame.height)
}) { (finished) in
// Animation completed; hide the image view
self.moveobj.isHidden = true
// Next animation
DispatchQueue.main.asyncAfter(deadline: .now() + 5){
self.moveobj.frame = originalFrame
self.moveobj.isHidden = false
}
}
}