I'm trying to make a card that slides while on swipe in swift - swift

I'm having a hard time with animations and PanGestureRecognizers, I want a view that slides by dragging up or dawn with your finger. It has 4 parts
1 - 2 the height, width and bottom constraints need to be changed
2 - 3 the height is the maximum height and you can slide it in or out
3 - 4 the card is at its maximum height and you can swipe it down
here is an image for a better understanding
thanks!

this is how I would do it.
var theView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
// First create your view.
theView = UIView(frame: CGRect(x: 40, y: self.view.bounds.height - 120, width: self.view.frame.width - 80, height: 100))
// set its background color
theView.backgroundColor = UIColor.black
// Create the round corners
theView.layer.cornerRadius = 20
// And add it to the view
self.view.addSubview(theView)
// Create the UIPanGestureRecognizer and assign the function to the selector
let gS = UIPanGestureRecognizer(target: self, action: #selector(growShink(_:)))
// Enable user interactions on the view
theView.isUserInteractionEnabled = true
// Add the gesture recognizer to the view
theView.addGestureRecognizer(gS)
}
// The control value is used to determine wether the user swiped up or down
var controlValue:CGFloat = 0
var animating = false
// The function called when the user swipes
#objc func growShink(_ sender:UIPanGestureRecognizer){
let translation = sender.location(in: theView)
// Check the control value to see if it has been set
if controlValue != 0 && !animating{
// if it has been set check that the control value is greater than the new value recieved by the pan gesture
if translation.x < controlValue{
animating = true
// If it is, change the values of the frame using animate
UIView.animate(withDuration: 0.5, animations: {
self.theView.frame = CGRect(x: 0, y: self.view.bounds.height - 300, width: self.view.frame.width, height: 300)
}, completion: {finished in
UIView.animate(withDuration: 1.0, animations: {
self.theView.frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height)
}, completion: {finished in self.animating = false; self.controlValue = 0})})
}else if translation.x > controlValue{
animating = true
// else, change the values of the frame back.
UIView.animate(withDuration: 0.5, animations: {
self.theView.frame = CGRect(x: 0, y: self.view.bounds.height - 300, width: self.view.frame.width, height: 300)
}, completion: {finished in
UIView.animate(withDuration: 1.0, animations: {
self.theView.frame = CGRect(x: 40, y: self.view.bounds.height - 120, width: self.view.frame.width - 80, height: 100)
}, completion: {finished in self.animating = false; self.controlValue = 0})})
}
}
// set the control value to the value received from the pan gesture
controlValue = translation.x
}
}
You can adjust the width and height values however you want.

Related

How to make the uiview not move after setting its layer's anchorPoint under Autolayout

I have a requirement that need change the UIView layer's anchorPoint, but the view cannot be moved after changing anchorPoint.
I know it is possible when the view is defined by frame(CGRect:...). like this:
let width = SCREEN_WIDTH - 40
let view2 = UIView(frame: CGRect(x: 20, y: 300, width: width, height: 200))
view2.backgroundColor = .blue
self.view.addSubview(view2)
let oldFrame2 = view2.frame
view2.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
view2.frame = oldFrame2
This works.
But my view is defined by Autolayout, I try the solution like above code, but it doesn't work. Code:
let view1 = UIView()
view1.backgroundColor = .orange
self.view.addSubview(view1)
view1.snp.makeConstraints { (maker) in
maker.top.equalToSuperview().offset(50)
maker.leading.equalToSuperview().offset(20)
maker.trailing.equalToSuperview().offset(-20)
maker.height.equalTo(200)
}
let oldFrame1 = view1.frame
view1.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
view1.frame = oldFrame1
The result is: the orange view1 be moved, it should be like the blue view2 after changing anchorPoint.
So can anyone give me some suggestions?
------------------------------Update Answer-----------------------------
Just as #DonMag answer, we can implement this requirement by updating the constraints of view not frame when using Autolayout. Here is the code by SnapKit:
let view1 = UIView()
view1.backgroundColor = .orange
view1.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(view1)
view1.snp.makeConstraints { (maker) in
maker.top.equalToSuperview().offset(100)
maker.leading.equalToSuperview().offset(20)
maker.trailing.equalToSuperview().offset(-20)
maker.height.equalTo(200)
}
// important!!!
view1.layoutIfNeeded()
let oldFrame1 = view1.frame
view1.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
// update constraints by updateConstraints function
// if you use #IBOutlet NSLayoutConstraint from xib,
// you can also just set xxx.constant = yyy to update the constraints.
view1.snp.updateConstraints { (maker) in
let subOffset = oldFrame1.width * 0.5
maker.leading.equalToSuperview().offset(20 - subOffset)
maker.trailing.equalToSuperview().offset(-20 - subOffset)
}
let width = SCREEN_WIDTH - 40
let view2 = UIView(frame: CGRect(x: 20, y: 300, width: width, height: 200))
view2.backgroundColor = .blue
self.view.addSubview(view2)
let oldFrame2 = view2.frame
view2.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
view2.frame = oldFrame2
Another solution is to change the autolayout view to frame by setting translatesAutoresizingMaskIntoConstraints = true, like this:
let width = SCREEN_WIDTH - 40
let view1 = UIView()
view1.backgroundColor = .orange
self.view.addSubview(view1)
// Autolayout
view1.snp.makeConstraints { (maker) in
maker.top.equalToSuperview().offset(100)
maker.leading.equalToSuperview().offset(20)
maker.trailing.equalToSuperview().offset(-20)
maker.height.equalTo(200)
}
// change autolayout to frame
view1.translatesAutoresizingMaskIntoConstraints = true
view1.frame = CGRect(x: 20, y: 100, width: width, height: 200)
let oldFrame1 = view1.frame
view1.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
view1.frame = oldFrame1
let view2 = UIView(frame: CGRect(x: 20, y: 300, width: width, height: 200))
view2.backgroundColor = .blue
self.view.addSubview(view2)
let oldFrame2 = view2.frame
view2.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
view2.frame = oldFrame2
First, when using auto-layout / constraints, setting the view's .frame directly will not give desired results. As soon as auto-layout updates the UI, the constraints will be re-applied.
When you change the .anchorPoint you change the geometry of the view. For that reason, you may be better off using .frame instead of auto-layout.
If you do need / want to use auto-layout, you'll need to update the .constant values of the constraints to account for the geometry changes.
I don't know how to do that with SnapKit, but here is an example using "standard" constraint syntax.
Declare Leading and Trailing constraint variables
assign and activate the constraints
tell auto-layout to calculate the frame
change the anchorPoint
update the Leading and Trailing constraint constants to reflect the geometry change
Note: this is example code only!:
class ViewController: UIViewController {
// these will have their .constant values changed
// to account for layer.anchorPoint change
var leadingConstraint: NSLayoutConstraint!
var trailingConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
// NOT using auto-layout constraints
let width = view.frame.width - 40
let view2 = UIView(frame: CGRect(x: 20, y: 300, width: width, height: 200))
view2.backgroundColor = .blue
self.view.addSubview(view2)
let oldFrame2 = view2.frame
view2.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
view2.frame = oldFrame2
// USING auto-layout constraints
let view1 = UIView()
view1.backgroundColor = .orange
view1.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(view1)
// create leading and trailing constraints
leadingConstraint = view1.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20.0)
trailingConstraint = view1.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20.0)
// activate constraints
NSLayoutConstraint.activate([
view1.topAnchor.constraint(equalTo: view.topAnchor, constant: 80.0),
view1.heightAnchor.constraint(equalToConstant: 200.0),
leadingConstraint,
trailingConstraint,
])
// auto-layout has not run yet, so force it to layout
// the view frame
view1.layoutIfNeeded()
// get the auto-layout generated frame
let oldFrame1 = view1.frame
// change the anchorPoint
view1.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
// we've move the X anchorPoint from 0.5 to 0.0, so
// we need to adjust the leading and trailing constants
// by 0.5 * the frame width
leadingConstraint.constant -= oldFrame1.width * 0.5
trailingConstraint.constant -= oldFrame1.width * 0.5
}
}
Result:

UITextField change width when clicking button

I want to make a search field in my app, and I was wondering if it is possible to make it invisible to begin with and then make it appear with the click of a button, like with the magnifying glass in the video below.
I have tried making the width of a UITextField 0 to begin with and then make a button make the width larger, but I am doing something wrong, and I can not figure out what. Maybe you could make an example in a blank project and show/link the code?
I hope you can help :)
UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseInOut, animations: {
//change view width
}) { (completed) in
//you can use completion or you can delete I am using it like that
self.searchTextview.becomeFirstResponder()
}
I am using this for my view. You can change for your need. Change constraints or width with animation
Function for expand the SearchTextField
func setTextField(setExpand:Bool = false){
self.txtfldSearch.delegate = self
self.txtfldSearch.borderStyle = UITextField.BorderStyle.none
self.txtfldSearch.translatesAutoresizingMaskIntoConstraints = true
let bottomLine = CALayer()
bottomLine.backgroundColor = UIColor.red.cgColor
UIView.animate(withDuration: 0.5) {
if setExpand{
self.txtfldSearch.frame = CGRect(x:
self.viewContainer.frame.origin.x + 8, y:
self.txtfldSearch.frame.origin.y, width:
(self.btnSearch.frame.origin.x -
(self.viewContainer.frame.origin.x + 16)),
height: self.txtfldSearch.frame.size.height)
bottomLine.frame = CGRect(x: 0.0, y:
self.txtfldSearch.frame.size.height-2, width:
self.txtfldSearch.frame.size.width, height: 2.0)
}
else{
self.txtfldSearch.frame = CGRect(x:
self.btnSearch.frame.origin.x - 8,
y: self.txtfldSearch.frame.origin.y, width: 0,
height:self.txtfldSearch.frame.size.height)
bottomLine.frame = CGRect(x: 0.0, y:
self.txtfldSearch.frame.size.height-2, width:
self.txtfldSearch.frame.size.width, height: 2.0)
}
}
self.txtfldSearch.layer.addSublayer(bottomLine)
}
Use of Code For Expanding pass true and other case pass false
self.setTextField(setExpand: true)

Swift - How to make animated arrow point in right direction?

I have two buttons which when connected, show a line with an animated arrow to show that they are connected.The issue is the arrow it self is not rotating appropriately to point in the right direction when animated.
How can I rotate the arrow to point in the right direction when moving?
class ViewController: UIViewController {
let line = CAShapeLayer()
let linePath = UIBezierPath()
var triangleImage = UIImage(named: "triangle" )
let startCoordinate = CGRect(x: 100, y: 100, width: 0, height: 0)
let btn1 = UIButton()
let btn2 = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
btn1.createRectangleButton(buttonPositionX: 100, buttonPositionY: 100, buttonWidth: 80, buttonHeight: 40, buttonTitle: "", buttonTag: 0)
btn2.createRectangleButton(buttonPositionX: 300, buttonPositionY: 400, buttonWidth: 80, buttonHeight: 40, buttonTitle: "", buttonTag: 1)
view.addSubview(btn1)
view.addSubview(btn2)
let imageView = UIImageView(image: triangleImage)
imageView.frame = CGRect(x:100, y:100, width: 20, height: 20)
imageView.center = self.btn1.center
view.addSubview(imageView)
linePath.move(to: btn1.center)
linePath.addLine(to: btn2.center)
line.path = linePath.cgPath
line.strokeColor = UIColor.red.cgColor
self.view.layer.addSublayer(line)
view.bringSubviewToFront(imageView)
UIView.animate(withDuration: 3, delay: 0.0, options: [.repeat, .curveLinear], animations: {
imageView.center = self.btn2.center
}, completion: nil)
}
note: the code above will look slightly different to the image
Try this rotation transformation
// rotation
imageView.transform = CGAffineTransform(rotationAngle: atan2(btn2.center.y - btn1.center.y, btn2.center.x - btn1.center.x) + CGFloat.pi / 2)
// used triangle image below

UIView pan gesture along Y axis

I have a UIView that I can drag and drop along the Y axis that snaps into 1 of 2 positions. The issue is that I have to drag it over a certain point in the Y axis in order for it to snap into place. This feels clunky when you try to quickly swipe but don't make it past that specific Y axis and it snaps back to the start position.
My desired goal is to have it snap into 1 of 2 positions depending on the direction the user was swiping when he released the UIView.
#objc func panGesture(recognizer: UIPanGestureRecognizer) {
let window = UIApplication.shared.keyWindow
let translation = recognizer.translation(in: self)
let currentY = self.frame.minY
let partialY = (window?.frame.height)!-194
let halfWayPoint = ((window?.frame.height)!/2)-40
self.frame = CGRect(x: 0, y: currentY + translation.y, width: self.frame.width, height: self.frame.height)
recognizer.setTranslation(CGPoint.zero, in: self)
switch recognizer.state {
case .changed:
// Prevent scrolling up past y:0
if currentY <= 0
{
self.frame = CGRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height)
}
case .ended:
// Snap to 80 (y) (expanded)
if currentY < 80 || currentY > 80 && currentY < halfWayPoint
{
UIView.animate(withDuration:0.62, delay: 0.0, usingSpringWithDamping: 0.62, initialSpringVelocity: 0.5, options: .curveEaseInOut, animations:
{
recognizer.view!.frame = CGRect(x: 0, y: 80, width: self.frame.width, height: self.frame.height)
}, completion: { (true) in
self.isExpanded = true
})
}
// Snap back to partial (y) (original position)
if currentY > partialY || currentY < partialY && currentY > halfWayPoint
{
UIView.animate(withDuration:0.62, delay: 0.0, usingSpringWithDamping: 0.62, initialSpringVelocity: 0.5, options: .curveEaseInOut, animations:
{
recognizer.view!.frame = CGRect(x: 0, y: partialY, width: self.frame.width, height: self.frame.height)
}, completion: {(true) in
self.isExpanded = false
})
}
default:
print("Default")
}
As you can see halfWayPoint is the point in which the view must be over or under in order to determine the position it snaps into. This is ineffective and I would like to do it depending on the direction (up or down) the user is dragging the view.
I suggest you use UIPanGestureRecognizers velicoty(in:) method.
It gives you the speed of the swipe (in points per second).
If you just add it to the currentY (preferably scaled with some constant), you will get that flicky feeling, and gesture will feel more natural.
Just replace
let currentY = self.frame.minY with let currentY = self.frame.minY + recognizer.velocity(in: nil).y * someConstant and play with someConstant until you get the feeling you want.

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
}
}
}