I have Two UIButtons on Right and Left and a UIView At Middle of the button..
I have to swipe ContainerView with some animation on UIButton click..
If user tap on right button ContainerView swipe to .right direction....If user tap on Left button ContainerView swipe to .left direction..
I have not found it anyWhere....Need help with full coding part
I have done this on my view
override func viewDidAppear(_ animated: Bool) {
leftSwipe()
rightSwipe()
}
//MARK:Left Swipe Function
func leftSwipe()
{
let swipeLeft = UISwipeGestureRecognizer()
swipeLeft.direction = .left
self.insideContainerViewSecond.addGestureRecognizer(swipeLeft)
swipeLeft.addTarget(self, action: #selector(swipe(sender:)))
}
//MARK:Right Swipe Function
func rightSwipe()
{
let swipeRight = UISwipeGestureRecognizer()
swipeRight.direction = .right
self.insideContainerViewSecond.addGestureRecognizer(swipeRight)
swipeRight.addTarget(self, action: #selector(swipe(sender:)))
}
//MARK: Swipe Function
#objc func swipe(sender: UISwipeGestureRecognizer)
{
switch sender.direction
{
case .left:
print("swiped left")
case .right:
print("swiped right")
default:
print("no action")
}
}
In this image I have a UIView and two button right and left..When I swipe on my UIView It swipe Left and right without animation.....But I need to swipe my UIView on right or Left button action with flip animation
We have CATransition Classes with iOS. You should use this. Or you should constraint animation. Maybe this repo is helped you for first trick.
https://github.com/barankaraoguzzz/FastOnBoarding/blob/master/FOView/Classes/FOAnimation.swift
if this answer doesn't be true for this question. Come again :)
You have a couple different questions...
How to "simulate" a swipe-gesture on a button tap?
You could do this different ways, such as creating a showNextImage() function, and then call it on swipe-left or on right-button-tap.
Or, you could do something like this, where you create a temporary swipe gesture recognizer and pass it to your recognizer function:
#objc func leftButtonTap(_ sender: UIButton)
{
// create a right-swipe-gesture
let sgr = UISwipeGestureRecognizer()
sgr.direction = .right
self.swipe(sender: sgr)
}
#objc func rightButtonTap(_ sender: UIButton)
{
// create a left-swipe-gesture
let sgr = UISwipeGestureRecognizer()
sgr.direction = .left
self.swipe(sender: sgr)
}
You said on swipe (or button tap), you want to use flip animation to show the next (or previous) image?
You can do this by adding two image views to your "container" view. Set one view hidden. When you want to "show the next image," set the .image property of the hidden imageView and then use UIView.transition(...) to flip from the visible imageView to the hidden imageView:
private func flipMe(_ direction: UIView.AnimationOptions) -> Void {
// fromView is the one that is NOT hidden
let fromView = imgViewB.isHidden ? imgViewA : imgViewB
// toView is the one that IS hidden
let toView = imgViewB.isHidden ? imgViewB : imgViewA
UIView.transition(from: fromView,
to: toView,
duration: 0.5,
options: [direction, .showHideTransitionViews],
completion: { b in
// if we want to do something on completion
})
}
Here is a simple example that you can run and see what happens. No #IBOutlet or #IBAction connections, so you don't need any Storyboard setup:
FlipView custom view subclass:
class FlipView: UIView {
// set image names from controller
var imageNames: [String] = [] {
didSet {
if let img = UIImage(named: imageNames[0]) {
imgViewB.image = img
} else {
imgViewB.image = testImage()
}
}
}
// index counter
private var idx: Int = 0
// two image views
private let imgViewA: UIImageView = UIImageView()
private let imgViewB: UIImageView = UIImageView()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
// add both image views
// with light-gray backgrounds
// constraining all 4 sides to self
[imgViewA, imgViewB].forEach { v in
v.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
addSubview(v)
v.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
v.topAnchor.constraint(equalTo: topAnchor),
v.leadingAnchor.constraint(equalTo: leadingAnchor),
v.trailingAnchor.constraint(equalTo: trailingAnchor),
v.bottomAnchor.constraint(equalTo: bottomAnchor),
])
}
// start with one of the image views hidden
imgViewA.isHidden = true
}
private func flipMe(_ direction: UIView.AnimationOptions) -> Void {
// fromView is the one that is NOT hidden
let fromView = imgViewB.isHidden ? imgViewA : imgViewB
// toView is the one that IS hidden
let toView = imgViewB.isHidden ? imgViewB : imgViewA
UIView.transition(from: fromView,
to: toView,
duration: 0.5,
options: [direction, .showHideTransitionViews],
completion: { b in
// if we want to do something on completion
})
}
func flipToPrevious() -> Void {
// don't try to flip past the first image
if idx == 0 {
return
}
// decrement the index
idx -= 1
let hiddenImageView = imgViewA.isHidden ? imgViewA : imgViewB
// get the previous image
if let img = UIImage(named: imageNames[idx]) {
hiddenImageView.image = img
} else {
hiddenImageView.image = testImage()
}
// flip it from left
flipMe(.transitionFlipFromLeft)
}
func flipToNext() -> Void {
// don't try to flip past the last image
if idx == imageNames.count - 1 {
return
}
// increment the index
idx += 1
let hiddenImageView = imgViewA.isHidden ? imgViewA : imgViewB
// get the next image
if let img = UIImage(named: imageNames[idx]) {
hiddenImageView.image = img
} else {
hiddenImageView.image = testImage()
}
// flip it from right
flipMe(.transitionFlipFromRight)
}
// get a numbered system image if the named image
// cannot be loaded from assets
private func testImage() -> UIImage {
let colors: [UIColor] = [
.systemRed, .systemGreen, .systemBlue,
.systemYellow, .systemTeal, .systemPurple,
]
guard let img = UIImage(systemName: "\(idx+1).circle") else {
// that should not fail, but in case it does
return UIImage()
}
return img.withTintColor(colors[idx % colors.count], renderingMode: .alwaysOriginal)
}
}
ViewController example ViewController class:
class ViewController: UIViewController {
let insideContainerViewSecond: FlipView = FlipView()
override func viewDidLoad() {
super.viewDidLoad()
// respect safe area
let g = view.safeAreaLayoutGuide
// create left and right buttons
let leftButton = UIButton()
leftButton.translatesAutoresizingMaskIntoConstraints = false
let leftImg = UIImage(systemName: "chevron.left.circle.fill")
leftButton.setImage(leftImg, for: [])
leftButton.addTarget(self, action: #selector(leftButtonTap(_:)), for: .touchUpInside)
let rightButton = UIButton()
rightButton.translatesAutoresizingMaskIntoConstraints = false
let rightImg = UIImage(systemName: "chevron.right.circle.fill")
rightButton.setImage(rightImg, for: [])
rightButton.addTarget(self, action: #selector(rightButtonTap(_:)), for: .touchUpInside)
insideContainerViewSecond.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(insideContainerViewSecond)
view.addSubview(leftButton)
view.addSubview(rightButton)
NSLayoutConstraint.activate([
insideContainerViewSecond.topAnchor.constraint(equalTo: g.topAnchor, constant: 80.0),
insideContainerViewSecond.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 80.0),
insideContainerViewSecond.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -80.0),
insideContainerViewSecond.heightAnchor.constraint(equalTo: insideContainerViewSecond.widthAnchor, multiplier: 0.5),
leftButton.centerYAnchor.constraint(equalTo: insideContainerViewSecond.topAnchor),
leftButton.centerXAnchor.constraint(equalTo: insideContainerViewSecond.leadingAnchor),
rightButton.centerYAnchor.constraint(equalTo: insideContainerViewSecond.topAnchor),
rightButton.centerXAnchor.constraint(equalTo: insideContainerViewSecond.trailingAnchor),
])
// your array of image names
let imageNames: [String] = [
"pic1", "pic2", "pic3", "pic4", "pic5", "pic6",
]
insideContainerViewSecond.imageNames = imageNames
leftSwipe()
rightSwipe()
}
//MARK:Left Swipe Function
func leftSwipe()
{
let swipeLeft = UISwipeGestureRecognizer()
swipeLeft.direction = .left
self.insideContainerViewSecond.addGestureRecognizer(swipeLeft)
swipeLeft.addTarget(self, action: #selector(swipe(sender:)))
}
//MARK:Right Swipe Function
func rightSwipe()
{
let swipeRight = UISwipeGestureRecognizer()
swipeRight.direction = .right
self.insideContainerViewSecond.addGestureRecognizer(swipeRight)
swipeRight.addTarget(self, action: #selector(swipe(sender:)))
}
//MARK: Swipe Function
#objc func swipe(sender: UISwipeGestureRecognizer)
{
switch sender.direction
{
case .left:
print("swiped left")
self.insideContainerViewSecond.flipToNext()
case .right:
print("swiped right")
self.insideContainerViewSecond.flipToPrevious()
default:
print("no action")
}
}
#objc func leftButtonTap(_ sender: UIButton)
{
// create a right-swipe-gesture
let sgr = UISwipeGestureRecognizer()
sgr.direction = .right
self.swipe(sender: sgr)
}
#objc func rightButtonTap(_ sender: UIButton)
{
// create a left-swipe-gesture
let sgr = UISwipeGestureRecognizer()
sgr.direction = .left
self.swipe(sender: sgr)
}
}
Related
Hi guys sorry for my English, I´m working on an app that is capable of moving a bunch of buttons, each of them, on-screen with UIPanGestureRecognizer, saving the center position (x,y) and tag in Core Data.
When the app starts from zero and I load position from Core Data:
if let c = ButtonEntity.getButton(tag: self.tag)
{
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5)
{
self.center = CGPoint(x: CGFloat( c.xPoint ?? 0), y: CGFloat( c.yPoint ?? 0))
}
This is from a custom UIButton Class and the buttons are placed by default in a certain position with auto layout
The problem is that in some cases some buttons return to their auto layout positions instead of coreData center position assigned on viewDidLoad
is there any solution or another way to do this?
thanks
To help understand why we cannot mix constraints with .center (frame) changes...
Here are two almost identical view controllers.
Each adds a button, and sets the button's .centerXAnchor and .centerYAnchor to the view's .centerXAnchor and .centerYAnchor.
We add a pan gesture to the button so we can drag it around.
We also implement touchesBegan(...) where all we do is change the button title. Doing so will trigger an auto-layout pass.
In the pan func in the first example, we use a typical "get the pan location and update the .center property:
#objc func pan(_ sender: UIPanGestureRecognizer) {
guard let v = sender.view, let sv = v.superview else { return }
if sender.state == .changed {
let pt: CGPoint = sender.location(in: sv)
// update the button's .center property (changes the frame)
v.center = pt
}
}
This works fine, until we tap anywhere off the button. At that point, we change the button title and auto-layout moves the button back to its center X and Y constraints.
In the second example, we add X and Y constraints as var / properties to the controller:
// btn center constraints
// we will modify the .constants when panning
var xConstraint: NSLayoutConstraint!
var yConstraint: NSLayoutConstraint!
set them up in viewDidLoad(), and then move the button in the pan gesture like this:
#objc func pan(_ sender: UIPanGestureRecognizer) {
guard let v = sender.view, let sv = v.superview else { return }
if sender.state == .changed {
let pt: CGPoint = sender.location(in: sv)
let xOff = pt.x - sv.center.x
let yOff = pt.y - sv.center.y
// update the .constant values for the btn center x/y
xConstraint.constant = xOff
yConstraint.constant = yOff
}
}
Now, tapping anywhere else will change the button title, but it will stay where it is because we've changed the .constant values of our X and Y constraints.
Set .center -- problems
class DemoVC: UIViewController {
let btnToMove = UIButton()
var tapCounter: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
btnToMove.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(btnToMove)
btnToMove.setTitle("Test", for: [])
btnToMove.backgroundColor = .red
NSLayoutConstraint.activate([
btnToMove.centerXAnchor.constraint(equalTo: view.centerXAnchor),
btnToMove.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
let p = UIPanGestureRecognizer(target: self, action: #selector(pan(_:)))
btnToMove.addGestureRecognizer(p)
}
#objc func pan(_ sender: UIPanGestureRecognizer) {
guard let v = sender.view, let sv = v.superview else { return }
if sender.state == .changed {
let pt: CGPoint = sender.location(in: sv)
// update the button's .center property (changes the frame)
v.center = pt
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// update the button title - which will trigger an auto-layout pass
tapCounter += 1
btnToMove.setTitle("Test \(tapCounter)", for: [])
}
}
Update constraint .constant values -- no problems
class DemoVC: UIViewController {
let btnToMove = UIButton()
var tapCounter: Int = 0
// btn center constraints
// we will modify the .constants when panning
var xConstraint: NSLayoutConstraint!
var yConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
btnToMove.setTitle("Test", for: [])
btnToMove.backgroundColor = .red
btnToMove.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(btnToMove)
xConstraint = btnToMove.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0.0)
yConstraint = btnToMove.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0.0)
NSLayoutConstraint.activate([
xConstraint, yConstraint,
])
let p = UIPanGestureRecognizer(target: self, action: #selector(pan(_:)))
btnToMove.addGestureRecognizer(p)
}
#objc func pan(_ sender: UIPanGestureRecognizer) {
guard let v = sender.view, let sv = v.superview else { return }
if sender.state == .changed {
let pt: CGPoint = sender.location(in: sv)
let xOff = pt.x - sv.center.x
let yOff = pt.y - sv.center.y
// update the .constant values for the btn center x/y
xConstraint.constant = xOff
yConstraint.constant = yOff
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// update the button title - which will trigger an auto-layout pass
tapCounter += 1
btnToMove.setTitle("Test \(tapCounter)", for: [])
}
}
I have a code where the user can input several ingredients and can also delete them.
Method: input ingredient in searchBar then create UIButton for that input and add into UIView. I also have a function to arrange position of all buttons properly
class SearchViewController: UIViewController {
var listInputView = UIView()
let spacingX: CGFloat = 10
let spacingY: CGFloat = 10
let btnHeight: CGFloat = 30
var heightConstraint: NSLayoutConstraint = NSLayoutConstraint()
var listBtn = [UIButton]()
//and also initialization of other components like UISearchBar,...
override func viewDidLoad() {
super.viewDidLoad()
setupSegmentControl()
setupView()
listInputView.translatesAutoresizingMaskIntoConstraints = false
heightConstraint = listInputView.heightAnchor.constraint(equalToConstant: 10)
}
func setupSegmentControl() {
//setup UISegmentedControl
}
fileprivate func setupView() {
//setup UISearchBar
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
if segmentControl.selectedSegmentIndex == 0 {
//search from input menu
}
else {
//search from input ingredients
inputSearchBar.append(mainSearchBar.text!)
listIngredients()
}
}
func listIngredients() {
view.addSubview(listInputView)
listInputView.isHidden = false
NSLayoutConstraint.activate([
listInputView.topAnchor.constraint(equalTo: mainSearchBar.bottomAnchor, constant: 10),
listInputView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
listInputView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
heightConstraint
])
createIngredientBtn(ingredient: mainSearchBar.text!)
}
// create a button
func createIngredientBtn(ingredient: String) {
let deleteIcon = UIImage(systemName: "multiply")
let btn = UIButton(type: UIButton.ButtonType.custom) as UIButton
btn.setTitle(ingredient, for: UIControl.State.normal)
btn.setTitleColor(UIColor(hexString: "#707070"),for: UIControl.State.normal)
btn.layer.borderColor = UIColor(hexString: "#707070").cgColor
btn.layer.borderWidth = 1.0
btn.frame.size.height = btnHeight
btn.layer.masksToBounds = true
btn.contentEdgeInsets = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8)
btn.setImage(deleteIcon, for: .normal)
btn.imageView?.contentMode = .scaleAspectFit
btn.imageEdgeInsets = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 3)
btn.semanticContentAttribute = UIApplication.shared.userInterfaceLayoutDirection == .rightToLeft ? .forceLeftToRight : .forceRightToLeft
btn.addTarget(self, action: #selector(deleteSelectedInput), for: UIControl.Event.touchDown)
btn.translatesAutoresizingMaskIntoConstraints = false
listBtn.append(btn)
listInputView.addSubview(btn)
}
// delete a button
#objc func deleteSelectedInput(_ sender: UIButton) {
sender.removeFromSuperview()
listBtn = listBtn.filter {$0 != sender}
showListBtn() //I try to call this here to reposition other buttons
viewDidLayoutSubviews()
inputSearchBar = inputSearchBar.filter {$0 != sender.currentTitle}
if inputSearchBar.isEmpty {
listInputView.isHidden = true
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
showListBtn()
}
//Arrange position of each button and update height of inputView according to them
func showListBtn() {
let listInputViewWidth = listInputView.frame.size.width
var currentOriginX: CGFloat = 0
var currentOriginY: CGFloat = 0
listBtn.forEach { btn in
// break to newline
if currentOriginX + btn.frame.width > listInputViewWidth {
currentOriginX = 0
currentOriginY += btnHeight + spacingY
}
// set the btn frame origin
btn.frame.origin.x = currentOriginX
btn.frame.origin.y = currentOriginY
// increment current X
currentOriginX += btn.frame.width + spacingX
}
// update listInputView height
heightConstraint.constant = currentOriginY + btnHeight
}
}
Problem: Suppose I have > 1 input ingredients (> 1 button) and I delete one of it, the rest will be overlapped on each other at the leftmost origin (where 1st button is placed). At this moment, the position of them is actually correct, they just don't re-display as they should be.
However, when I try to input new ingredient, they will then display properly.
Here is the image to clarify this better:
Input 3 ingredients: Ing1, Ing2, Ing3
Delete Ing1. Then Ing2 and Ing3 are overlapped at the position of Ing1.
Add new input Ing4. They are all position correctly again.
Why the buttons are not re-position correctly when I delete one of them and how can I solve this ? Please kindly help me. Thank you.
EDIT: I'm able to call viewDidLayoutSubviews after delete but the buttons still not re-draw at new position although the frame.origin has changed correctly.
#objc func deleteSelectedInput(_ sender: UIButton) {
sender.removeFromSuperview()
listBtn = listBtn.filter {$0 != sender}
listInputView.removeAllSubviews()
listBtn.forEach { btn in
listInputView.addSubview(btn)
}
inputSearchBar = inputSearchBar.filter {$0 != sender.currentTitle}
if inputSearchBar.isEmpty {
listInputView.isHidden = true
}
}
This is what the code (below) can look like:
import UIKit
class TornadoButton: UIButton {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let pres = self.layer.presentation()!
let suppt = self.convert(point, to: self.superview!)
let prespt = self.superview!.layer.convert(suppt, to: pres)
return super.hitTest(prespt, with: event)
}
}
class TestViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let greySubview = UIView()
greySubview.backgroundColor = .red
greySubview.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(greySubview)
greySubview.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
greySubview.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
greySubview.widthAnchor.constraint(equalTo: self.view.widthAnchor).isActive = true
greySubview.heightAnchor.constraint(equalTo: greySubview.widthAnchor).isActive = true
let button = TornadoButton()
greySubview.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
button.widthAnchor.constraint(equalTo: greySubview.widthAnchor, multiplier: 0.09).isActive = true
button.heightAnchor.constraint(equalTo: greySubview.heightAnchor, multiplier: 0.2).isActive = true
//below constrains are needed, else the origin of the UIBezierPath is wrong
button.centerXAnchor.constraint(equalTo: greySubview.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: greySubview.centerYAnchor, constant: self.view.frame.height * 0.35).isActive = true
self.view.layoutIfNeeded()
button.addTarget(self, action: #selector(tappedOnCard(_:)), for: .touchUpInside)
let circlePath = UIBezierPath(arcCenter: greySubview.frame.origin, radius: CGFloat(greySubview.frame.width * 0.5), startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: true)
let orbit = CAKeyframeAnimation(keyPath: "position")
orbit.duration = 12
orbit.path = circlePath.cgPath
orbit.isAdditive = true
orbit.repeatCount = Float.greatestFiniteMagnitude
orbit.calculationMode = kCAAnimationPaced
orbit.rotationMode = kCAAnimationRotateAuto
button.layer.add(orbit, forKey: "orbit")
button.backgroundColor = .blue
let gr = UITapGestureRecognizer(target: self, action: #selector(onTap(gesture:)))
greySubview.addGestureRecognizer(gr)
}
#objc func onTap(gesture:UITapGestureRecognizer) {
let p = gesture.location(in: gesture.view)
let v = gesture.view?.hitTest(p, with: nil)
}
#IBAction func tappedOnCard(_ sender: UIButton) {
print(sender)
}
}
This almost works, BUT:
I can retrieve the button if the button is 100% visible on the screen. If it is, for example, 50% visible (and 50% off the screen (look picture below)), I do not retrieve the buttons tap (aka not retrieve the print).
How can I retrieve the button while it is in a running animation and it can be slightly off the screen?
Original Answer:
It is very likely that the UITapGestureRecognizer in greySubview is consuming the touch and not letting it pass through to the button. Try this:
gr.cancelsTouchesInView = NO;
before adding the UITapGestureRecognizer to greySubview.
Edited Answer:
Please ignore my earlier answer and revert the change if you did it. My original answer did not make sense because in your case, the button is the subview of the grayView and cancelsTouchesInView works upwards in the view hierarchy, not downwards.
After a lot of digging, I was able to figure out why this was happening. It was because of incorrect hit testing in the TornadoButton during animation. Since you are animating the button, you will need to override the hitTest and the pointInside methods of the TornadoButton so that they account for the animated position instead of the actual position of the button when hit testing.
The default implementation of the pointInside method does not take into account the animated presentation layer of the button, and just tries to check for the touch point within the rect (which will fail because the touch was somewhere else).
The following implementations of the methods seem to do the trick:
class TornadoButton: UIButton{
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let pres = self.layer.presentation()!
let suppt = self.convert(point, to: self.superview!)
let prespt = self.superview!.layer.convert(suppt, to: pres)
if (pres.hitTest(suppt)) != nil{
return self
}
return super.hitTest(prespt, with: event)
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
let pres = self.layer.presentation()!
let suppt = self.convert(point, to: self.superview!)
return (pres.hitTest(suppt)) != nil
}
}
I don't quite get, why you use buttons for showing a simple rectangle when views would work as well, but anyways... You didn't show us where you add the target to your button.
button.addTarget(self, action: #selector(ViewController.buttonPressed(_:)), for: .touchUpInside) // This is needed that your button 'does something'
button.tag = 1 // This is a tag that you can use to identify which button has been pressed
with the function buttonPressed looking something like this:
#objc func buttonPressed(_ button: UIButton) {
switch button.tag {
case 1: print("First button was pressed")
case 2: print("Second button was pressed")
default: print("I don't know the button you pressed")
}
}
I´m trying to make an status banner animate on top of a navigationBar.
The animation works fine if I add the banner on the viewControllers view, but will end up behind the navigation bar. If I add the banner to the navigation bar, the banner "pop" on the navigationBar, but will not preform animation. Same problem if I add the banner view to the keyWindow.
I also tried to manipulate the banner view and the navigationBars layers zPosition without any luck.
Anyone hav an idea?
Here is my code...
import UIKit
class BellaBannerView : UIView {
var view : UIView!
var style : BannerStyle!
var position : BannerTopAnchor!
var bannerView : UIView!
var messageLabel : UILabel!
var dissmissButton : UIButton!
var offsetConstraintConstant : CGFloat!
var testconst : NSLayoutConstraint!
override init(frame: CGRect) {
super.init(frame: frame)
}
convenience init(view: ViewController, style : BannerStyle, pos : BannerTopAnchor) {
self.init(frame: CGRect.zero)
self.view = view.view
self.style = style
self.position = pos
self.offsetConstraintConstant = style.bannerSize()
self.translatesAutoresizingMaskIntoConstraints = false
self.clipsToBounds = true
if let isNavigation = view.navigationController?.view {
isNavigation.addSubview(self)
} else {
self.view.addSubview(self)
}
initViews(style: style, pos: pos)
addSubview(bannerView)
bannerView.addSubview(dissmissButton)
bannerView.addSubview(messageLabel)
setConstraints(view: view, pos: pos)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func initViews(style : BannerStyle, pos : BannerTopAnchor) {
bannerView = {
let view = UIView()
view.backgroundColor = style.backgroundColor()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
dissmissButton = {
let btn = UIButton()
btn.translatesAutoresizingMaskIntoConstraints = false
btn.backgroundColor = style.backgroundColor()
btn.setImage(UIImage(named: "dissmiss"), for: .normal)
btn.addTarget(self, action: #selector(hide), for: .allTouchEvents)
return btn
}()
messageLabel = {
let label = UILabel()
label.text = "BESKJED beskjed Beskjed"
label.textColor = .lightGray
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
}
func show() {
UIView.animate(withDuration: 0.5, animations: { () -> Void in
self.testconst.constant = 0
self.view.layoutIfNeeded()
})
}
func hide() {
UIView.animate(withDuration: 0.5, animations: { () -> Void in
self.testconst.constant = -self.offsetConstraintConstant
self.view.layoutIfNeeded()
})
}
func setConstraints(view : ViewController, pos : BannerTopAnchor) {
self.heightAnchor.constraint(equalToConstant: style.bannerSize()).isActive = true
self.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width).isActive = true
self.topAnchor.constraint(equalTo: self.view.topAnchor, constant: pos.topPosition()).isActive = true
self.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
bannerView.heightAnchor.constraint(equalTo: self.heightAnchor).isActive = true
bannerView.widthAnchor.constraint(equalTo: self.widthAnchor).isActive = true
bannerView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
self.testconst = bannerView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -self.offsetConstraintConstant)
self.testconst.isActive = true
dissmissButton.bottomAnchor.constraint(equalTo: bannerView.bottomAnchor).isActive = true
dissmissButton.trailingAnchor.constraint(equalTo: bannerView.trailingAnchor).isActive = true
dissmissButton.widthAnchor.constraint(equalToConstant: style.buttonSize()).isActive = true
dissmissButton.heightAnchor.constraint(equalToConstant: style.buttonSize()).isActive = true
messageLabel.leadingAnchor.constraint(equalTo: bannerView.leadingAnchor, constant: style.buttonSize()).isActive = true
messageLabel.trailingAnchor.constraint(equalTo: bannerView.trailingAnchor, constant: -style.buttonSize()).isActive = true
messageLabel.bottomAnchor.constraint(equalTo: bannerView.bottomAnchor).isActive = true
messageLabel.heightAnchor.constraint(equalToConstant: style.bannerSize()).isActive = true
}
enum BannerTopAnchor {
case top
case statusBar
case navbar
func topPosition() -> CGFloat {
switch self {
case .top:
return 0
case .statusBar:
return UIApplication.shared.statusBarFrame.height
case .navbar:
return 64
}
}
}
enum BannerStyle {
case success
case info
case offline
case online
func backgroundColor() -> UIColor {
switch self {
case .success, .online:
return UIColor.green//.bellaSuccessBannerBackground()
case .info:
return UIColor.blue//.bellaDarkishBlueColor()
case .offline:
return UIColor.red//.bellaLipstickColor()
}
}
func bannerSize() -> CGFloat {
switch self {
case .info:
return 50
case .online, .offline, .success:
return 25
}
}
func buttonSize() -> CGFloat {
switch self {
case .info:
return 50
default:
return 0
}
}
func textColor() -> UIColor {
switch self {
case .success, .online:
return UIColor.green//bellaDarkishGreenColor()
case .info:
return UIColor.brown//bellaBeigeColor()
case .offline:
return UIColor.white
}
}
func dismissable() -> Bool {
switch self {
case .success, .offline, .online:
return false
case .info:
return true
}
}
}
}
Problem fixed. Updated layout before starting the animation
func show() {
self.layoutIfNeeded()
self.testconst.constant = 0
UIView.animate(withDuration: 0.5, animations: { () -> Void in
self.layoutIfNeeded()
})
}
func hide() {
self.layoutIfNeeded()
self.testconst.constant = -self.offsetConstraintConstant
UIView.animate(withDuration: 0.5, animations: { () -> Void in
self.layoutIfNeeded()
})
}
So the problem here is that the navigation controller take your view controller's view and adds it to it's content view, which is behind the navigation bar. You need to add your banner to something that is above the navigation bar.
You have a few options here, you can add it to the navigation controllers view directly:
self.navigationController?.view.addSubview(bannerView)
this should work fine, though I am always hesitant to play with a UINavigationController view hierarchy manually, as I always see it as a manager hierarchy.
In my opinion a better approach would be to add it straight to the window, which will have the added benefit of making sure it is on top of everything:
self.view.window?.addSubview(bannerView)
this accesses the root window that self's view is in, and adds your banner to that, on top of everything.
class PhotoViewController: UIViewController {
override var prefersStatusBarHidden: Bool {
return true
}
private var backgroundImage: UIImage
init(image: UIImage) {
self.backgroundImage = image
super.init(nibName: nil, bundle: nil)
print(image)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.gray
let backgroundImageView = UIImageView(frame: view.frame)
backgroundImageView.image = backgroundImage
backgroundImageView.isUserInteractionEnabled = true
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(panGestureRecognizerAction(_:)))
backgroundImageView.addGestureRecognizer(panGestureRecognizer)
view.addSubview(backgroundImageView)
}
func panGestureRecognizerAction(_ gesture: UIPanGestureRecognizer){
let translation = gesture.translation(in: view)
view.frame.origin.y = translation.y
if gesture.state == .ended{
let velocity = gesture.velocity(in: view)
if velocity.y > 1500{
UIView.animate(withDuration: 0.5, animations: {
self.dismiss(animated: true, completion: nil)
})
} else{
UIView.animate(withDuration: 0.3, animations: {
self.view.frame.origin = CGPoint(x: 0, y: 0)
})
}
}
}
My entire view moves and reveals a black background behind it. How do i get the backgroundImageView to move instead? I am trying to display a view underneath. If any other info is required please let me know.
In your panGestureRecognizerAction you are asking it to move view, which is the view of your viewController. Instead, use the view assigned to the gestureRecognizer by saying:
if let bgImage = gesture.view {
bgImage.frame.origin.y = translation.y
}
gesture.view is an optional, which is why I check it first. You could also say gesture.view?.frame.origin.y = translation.y and that'd work just fine too.