swift 中设备方向改变时不显示浮动操作按钮的视图

View of floating action button not appearing when device orientation changes in swift

我正在尝试以编程方式在 swift 中创建一个浮动操作按钮。我使用在 youtube 上找到的这段视频制作了一个视频,尽管存在一些错误,但它仍然有效。另一个问题是视频是印地语的,所以有些部分我不完全理解。 https://www.youtube.com/watch?v=xArCnGGzZEE

虽然这是一个很好的视频,但我正在尝试解决一些问题以供自己使用。第一个是当按下浮动按钮时出现模糊视图,但它只适用于纵向。我设法解决了这个问题,但另一个问题让我迷路了。

因此浮动操作按钮起作用了,并且出现了包含几个按钮的清晰视图。问题是当方向随着按钮的消失而改变时,我改变了方向按钮消失了。如果我将方向改回我按下浮动操作按钮的方向,带有按钮的视图将再次出现。这意味着视图要去某个地方,我试图找到它,但我无法弄清楚如何在没有导致错误的约束的情况下找到它。

为了帮助证明我的观点,这就是视图的样子。

这是我按下浮动操作按钮时的样子,效果很好

这就是问题所在。当我改变设备方向时,我把相机和照片按钮放在外面了。

如果我将方向改回纵向,相机和照片按钮会返回,这意味着按钮的位置会移出视野。

这也很奇怪,因为如果我在设备处于横向时按下浮动操作按钮,它会很好地工作。

然后我将方向更改为纵向,相机和照片按钮也消失了。我知道它应该还在那里,因为模糊视图仍然有效。

这是按钮的视图。

import Foundation
import UIKit

public typealias CameraButtonAction = (CameraButton) -> Void

open class CameraButton: NSObject {

    //the action the button should perform when tapped
    var action: CameraButtonAction?

    //the button's background color: set default color and selected color
    var backgroundColor: UIColor = UIColor.darkGray {
        willSet {
            floatButton.backgroundColor = newValue
            backgroundColorSelected = newValue
        }
    }

    //the button's background color: set default color
    var backgroundColorSelected: UIColor = UIColor.darkGray

    //indicates if the button is active
    var active: Bool = false


    //an array of items that the button will present
    var items: [CameraButtonItem]? {
        willSet {
            if let item = self.items {
                for abi in item {
                    abi.view.removeFromSuperview()
                }
            }
        }
        didSet {
            placeButtonItems()
            showActive(true)
        }
    }

    //the button that will be presented to the user
    var floatButton: UIButton!

    //view that will hold the placement of the button's actions
    var contentView: UIView!

    //view where the floatbutton will be displayed
    var parentView: UIView!

    //blur effect that will be presented when the button is active
    var blurVisualEffect: UIVisualEffectView!

    //distance between each item action
    let itemOffset = -55

    //the float button's radius
    let floatBtnRadius = 50

    public init(attachedToView view: UIView, items: [CameraButtonItem]?) {
        super.init()

        //creates the float button
        self.parentView = view
        self.items = items
        let bounds = self.parentView.bounds

        self.floatButton = UIButton(type: .custom)
        self.floatButton.layer.cornerRadius = CGFloat(floatBtnRadius / 2)
        self.floatButton.layer.shadowOpacity = 1
        self.floatButton.layer.shadowRadius = 2
        self.floatButton.layer.shadowOffset = CGSize(width: 1, height: 1)
        self.floatButton.layer.shadowColor = UIColor.gray.cgColor

        let cameraImg = UIImage(named: "otherCamera")
        self.floatButton.setImage(cameraImg, for: UIControl.State())
        self.floatButton.backgroundColor = self.backgroundColor
        self.floatButton.contentEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 8, right: 0)
        self.floatButton.isUserInteractionEnabled = true
        self.floatButton.translatesAutoresizingMaskIntoConstraints = false

        self.floatButton.addTarget(self, action: #selector(CameraButton.buttonTapped(_:)), for: .touchUpInside)
        self.floatButton.addTarget(self, action: #selector(CameraButton.buttonTouchDown(_:)), for: .touchDown)
        self.parentView.addSubview(self.floatButton)


        self.contentView = UIView(frame: bounds)


        self.blurVisualEffect = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
        blurVisualEffect.translatesAutoresizingMaskIntoConstraints = false
        self.blurVisualEffect.frame = self.contentView.frame
        self.contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        self.blurVisualEffect.autoresizingMask = [.flexibleWidth, .flexibleHeight]

        self.contentView.addSubview(self.blurVisualEffect)
        self.contentView.insertSubview(self.blurVisualEffect, at: 0)
        let tap = UITapGestureRecognizer(target: self, action: #selector(CameraButton.backgroundTapped(_:)))
        self.contentView.addGestureRecognizer(tap)

        self.installConstraints()
    }


    required public init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }



    func setImage(_ image: UIImage?, forState state: UIControl.State) {
        floatButton.setImage(image, for: state)
        floatButton.adjustsImageWhenHighlighted = false
        floatButton.contentEdgeInsets = UIEdgeInsets.zero
    }

    //MARK: - Auto Layout Methods
    //install all the necessary constraints for the button. By the default the button will be placeed at 15pts from the bottom and the 15 pts from the right of its parentView

    func installConstraints() {
        let views: [String: UIView] = ["floatButton":self.floatButton, "parentView":self.parentView]
        let width = NSLayoutConstraint.constraints(withVisualFormat: "H:[floatButton(\(floatBtnRadius))]", options: NSLayoutConstraint.FormatOptions.alignAllCenterX, metrics: nil, views: views)
        let height = NSLayoutConstraint.constraints(withVisualFormat: "V:[floatButton(\(floatBtnRadius))]", options: NSLayoutConstraint.FormatOptions.alignAllCenterX, metrics: nil, views: views)
        self.floatButton.addConstraints(width)
        self.floatButton.addConstraints(height)

        let trailingSpacing = NSLayoutConstraint.constraints(withVisualFormat: "V:[floatButton]-15-|", options: NSLayoutConstraint.FormatOptions.alignAllCenterX, metrics: nil, views: views)
        let bottomSpacing = NSLayoutConstraint.constraints(withVisualFormat: "H:[floatButton]-15-|", options: NSLayoutConstraint.FormatOptions.alignAllCenterX, metrics: nil, views: views)
        self.parentView.addConstraints(trailingSpacing)
        self.parentView.addConstraints(bottomSpacing)


    }


    //MARK: - Button Actions Methods
    @objc func buttonTapped(_ sender: UIControl) {
        animatePressingWithScale(1.0)

        if let unwrappedAction = self.action {
            unwrappedAction(self)
        }
    }

    @objc func buttonTouchDown(_ sender: UIButton) {
        animatePressingWithScale(0.9)
    }

    //MARK: -Gesture Recognizer Methods
    @objc func backgroundTapped(_ gesture: UIGestureRecognizer) {
        if self.active {
            self.toggle()
        }
    }

    //MARK: - Custom methods

    //presents or hides all the actionButtons actions
    func toggleMenu() {
        self.placeButtonItems()
        self.toggle()
    }

    //MARK: Action Button Item Placement
    //defines the posistion of all the actionButton's actions
    func placeButtonItems() {
        if let optionalItems = self.items {
            for item in optionalItems {
                item.view.center = CGPoint(x: self.floatButton.center.x - 83, y: self.floatButton.center.y)
                //item.view.removeFromSuperview()
                self.contentView.addSubview(item.view)
            }
        }
    }


    //MARK: - Float menu Methods
    //presents or hides all the actionButton's actions and changes the active state
    func toggle() {
        //print("First: \(active)")
        self.animateMenu()
        self.showBlur()

        self.active = !self.active
        self.floatButton.backgroundColor = self.active ? backgroundColorSelected : backgroundColor
        self.floatButton.isSelected = self.active
        print("Second: \(active)")
    }

    func animateMenu() {
        UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0.1, options: UIView.AnimationOptions.allowAnimatedContent, animations: {
            self.showActive(false)
        }, completion: {completion in
            if self.active == false {
                //self.hideBlur()
            }
        })
    }

    func showActive(_ active: Bool) {
        if self.active == active {
            self.contentView.alpha = 1.0

            if let optionalItems = self.items {
                for (index, item) in optionalItems.enumerated() {
                    let offset = index + 1
                    let translation = self.itemOffset * offset
                    item.view.transform = CGAffineTransform(translationX: 0, y: CGFloat(translation))
                    item.view.alpha = 1
                }
            }
        } else {
            self.contentView.alpha = 0.0
            if let optionalItems = self.items {
                for item in optionalItems {
                item.view.transform = CGAffineTransform(translationX: 0, y: 0)
                item.view.alpha = 0
                }
            }
        }
    }

    func showBlur() {
        self.parentView.insertSubview(self.contentView, belowSubview: self.floatButton)
    }

    func hideBlur() {
        self.contentView.removeFromSuperview()
    }

    //animating the button by pressing, by the default this method just scales the button down when its pressed and returns to its normal size when the button is no longer pressed
    //parameter scale: how much the button should be scaled
    func animatePressingWithScale(_ scale: CGFloat) {
        UIView.animate(withDuration: 0.2, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0.1, options: UIView.AnimationOptions.allowAnimatedContent, animations: {
            self.floatButton.transform = CGAffineTransform(scaleX: scale, y: scale)
        }, completion: nil)
    }

}

这是按钮项的视图

import Foundation
import UIKit

public typealias CameraButtonItemAction = (CameraButtonItem) -> (Void)


open class CameraButtonItem: NSObject {

    //the action the item should perform when tapped
    open var action: CameraButtonItemAction?

    //Description of the item's action
    open var text: String {
        get {
            return self.label.text!
        }
        set {
            self.label.text = newValue
        }
    }

    //view that will hold the item's button and label
    var view: UIView!

    //label that contain the item's text
    var label: UILabel!

    //main button that will perform the defined action
    var button: UIButton!

    //image used by the button
    var image: UIImage!

    //size needed for the view property present the item's content
    let viewSize = CGSize(width: 200, height: 35)

    //button's size by default the button is 35x35
    let buttonSize = CGSize(width: 35, height: 35)

    var labelBackground: UIView!
    let backgroundInset = CGSize(width: 10, height: 10)

    public init(title optionalTitle: String?, image: UIImage?) {
        super.init()
        //so when the button is pressed a view will appear with buttons and labels that can be pressed. The view has no background color.
        self.view = UIView(frame: CGRect(origin: CGPoint.zero, size: self.viewSize))
        self.view.alpha = 0
        self.view.isUserInteractionEnabled = true
        //self.view.backgroundColor = UIColor.purple


        //this creates the button that we can press
        self.button = UIButton(type: .custom)
        self.button.frame = CGRect(origin: CGPoint(x: self.viewSize.width - self.buttonSize.width, y: 0), size: buttonSize)
        self.button.layer.shadowOpacity = 1
        self.button.layer.shadowRadius = 2
        self.button.layer.shadowOffset = CGSize(width: 1, height: 1)
        self.button.layer.shadowColor = UIColor.black.cgColor
        self.button.addTarget(self, action: #selector(CameraButtonItem.buttonPressed(_:)), for: .touchUpInside)

        if let unwrappedImage = image {
            self.button.setImage(unwrappedImage, for: UIControl.State())
        }

        if let text = optionalTitle, text.trimmingCharacters(in: CharacterSet.whitespaces).isEmpty == false {
            self.label = UILabel()
            self.label.font = UIFont(name: "HelveticaNeue-Medium", size: 13)
            self.label.textColor = UIColor.black
            self.label.textAlignment = .center
            self.label.text = text
            self.label.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(CameraButtonItem.labelTapped(_:))))
            self.label.sizeToFit()

            self.labelBackground = UIView()
            self.labelBackground.frame = self.label.frame
            self.labelBackground.backgroundColor = UIColor.white
            self.labelBackground.layer.cornerRadius = 5
            self.labelBackground.layer.shadowOpacity = 0.8
            self.labelBackground.layer.shadowOffset = CGSize(width: 0, height: 1)
            self.labelBackground.layer.shadowRadius = 0.2
            self.labelBackground.layer.shadowColor = UIColor.lightGray.cgColor

            //Adjust the label's background inset
            self.labelBackground.frame.size.width = self.label.frame.size.width + backgroundInset.width
            self.labelBackground.frame.size.height = self.label.frame.size.height + backgroundInset.height
            self.label.frame.origin.x = self.label.frame.origin.x + backgroundInset.width / 2
            self.label.frame.origin.y = self.label.frame.origin.y + backgroundInset.height / 2

            //adjust the label's background position
                //distance between the button and the label
            self.labelBackground.frame.origin.x = CGFloat(130 - self.label.frame.size.width)
            self.labelBackground.center.y = self.view.center.y
            self.labelBackground.addSubview(self.label)

            //Add Tap Gesture Recognizer
            let tap = UITapGestureRecognizer(target: self, action: #selector(CameraButtonItem.labelTapped(_:)))
            self.view.addGestureRecognizer(tap)

            self.view.addSubview(self.labelBackground)
        }
        self.view.addSubview(self.button)
    }


    //MARK: -Button Action Methods
    @objc func buttonPressed(_ sender: UIButton) {
        if let unwrappedAction = self.action {
            unwrappedAction(self)
        }
    }

    //MARK: - Gesture Recognizer Methods
    @objc func labelTapped(_ gesture: UIGestureRecognizer) {
        if let unwrappedAction = self.action {
            unwrappedAction(self)
        }
    }


}

这是我在集合视图中设置按钮的viewcontroller

import UIKit

extension ViewController: UICollectionViewDelegateFlowLayout {

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: 100, height: 150)
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
        return UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
    }
}


extension ViewController: UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return items.count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! TestingCollectionViewCell
        cell.backgroundColor = .white
        let img = UIImage(named: self.items[indexPath.row])
        cell.imageView.image = img
        cell.imageName.text = "\(self.items[indexPath.row])"
        return cell
    }


    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        print("\(items[indexPath.row])")
        self.presentInfoView(withInfo: items[indexPath.row])
    }
}

class ViewController: UIViewController, BlurDelegate {

    var actionButton: CameraButton!




    func removeBlurView() {
        for subview in view.subviews {
            if subview.isKind(of: UIVisualEffectView.self) {
                subview.removeFromSuperview()
                self.infoView.removeFromSuperview()
            }
        }
    }


    fileprivate var items: [String] = [
        "photo1",
        "photo2",
        "photo3",
        "photo4",
        "photo5",
        "photo6",
        "photo7",
        "photo8",
        "photo9",
        "photo10",
        "photo11",
        "photo12",
        "photo13",
        "photo14",
        "photo15",
        "photo16",
        "photo17",
        "photo18",
        "photo19",
        "photo20",
        "photo21",
        "photo22",
        "photo23",
        "photo24"
    ]

    override func viewDidLoad() {
        super.viewDidLoad()

        let collection = UICollectionView(frame: view.frame, collectionViewLayout: UICollectionViewFlowLayout())
                //allows us to use auto layout constraints
        collection.translatesAutoresizingMaskIntoConstraints = false
        collection.backgroundColor = .black
        view.addSubview(collection)
        collection.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        collection.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
        collection.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
        collection.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true

        collection.dataSource = self
        collection.delegate = self

        collection.register(TestingCollectionViewCell.self, forCellWithReuseIdentifier: "cell")
        self.navigationItem.title = "Testing"
        setUpButton()

    }

    func setUpButton() {
        let cameraBtn = UIImage(named: "CameraBtn")
        let photoBtn = UIImage(named: "PhotoBtn")
        let cameraRound = UIImage(named: "otherCamera")
        let camera = CameraButtonItem(title: "Camera", image: cameraBtn)
        let photo = CameraButtonItem(title: "Photo", image: photoBtn)
        camera.action = { item in
            print(item)
            print("Camera Btn Pressed")
        }
        photo.action = { item in
            print(item)
            print("Photo Btn Pressed")
        }

        actionButton = CameraButton(attachedToView: self.view, items: [camera, photo])
        actionButton.setImage(cameraRound, forState: UIControl.State())
        actionButton.backgroundColor = .green
        actionButton.action = { button in button.toggleMenu()
            //self.setBlurView()
        }
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        var touch: UITouch? = touches.first
        if touch?.view != infoView {
            dismissView()

        }
    }




    func dismissView() {

        //dismiss(animated: true, completion: nil)
        removeBlurView()

    }

    func setBlurView() {
        let blurView = UIVisualEffectView()
        blurView.frame = view.frame
        blurView.effect = UIBlurEffect(style: .regular)
        blurView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.addSubview(blurView)
    }

    func presentInfoView(withInfo info: String) {
        setBlurView()
        view.addSubview(infoView)
        //infoView.addView()
        let img = UIImage(named: info)
        infoView.imageView.image = img
        infoView.nameLbl.text = info


        infoView.backgroundColor = .white
        infoView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        infoView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.8).isActive = true
        //infoView.widthAnchor.constraint(equalToConstant: view.frame.width - 64).isActive = true
        infoView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.8).isActive = true
        //infoView.heightAnchor.constraint(equalToConstant: view.frame.height - 64).isActive = true
        infoView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -44).isActive = true
        infoView.transform = CGAffineTransform(scaleX: 1.3, y: 1.3)

        infoView.alpha = 0

        UIView.animate(withDuration: 0.5) {
            self.infoView.alpha = 1
            self.infoView.transform = .identity
        }
    }

}

我试图做的是为视图和浮动操作按钮添加约束,但它导致整个事情崩溃。我还尝试添加一个功能,当设备方向改变时删除当前约束并为特定方向添加新约束,但没有真正的区别。

如果我能提供更多信息,请询问。 非常感谢

update this constraint

 let trailingSpacing = NSLayoutConstraint.constraints(withVisualFormat: "V:[floatButton]-20-|", options: NSLayoutConstraint.FormatOptions.alignAllCenterX, metrics: nil, views: views)
 let bottomSpacing = NSLayoutConstraint.constraints(withVisualFormat: "H:[floatButton]-25-|", options: NSLayoutConstraint.FormatOptions.alignAllCenterX, metrics: nil, views: views)

我 fork 源代码并更新了 HERE

浮动按钮的第一件事,在 iPhone X, 11, ... safeArea 比正常情况下更大,因此我们必须使用 safeAreaLayoutGuide 添加约束,如下代码所示:

/// the float button's trailing padding
    fileprivate let floatButtonTrailingPadding: CGFloat = 15

/// the float button's bottom padding
fileprivate let floatButtonBottomPadding: CGFloat = 15

if #available(iOS 11.0, *) {
    let guide = self.parentView.safeAreaLayoutGuide
    self.floatButton.trailingAnchor.constraint(equalTo: guide.trailingAnchor, constant: -(floatButtonTrailingPadding)).isActive = true
    self.floatButton.bottomAnchor.constraint(equalTo: guide.bottomAnchor, constant: floatButtonBottomPadding).isActive = true
}
else {
    let trailingSpacing = NSLayoutConstraint.constraints(withVisualFormat: "V:[floatButton]-\(floatButtonTrailingPadding)-|", options: NSLayoutConstraint.FormatOptions.alignAllCenterX, metrics: nil, views: views)
    let bottomSpacing = NSLayoutConstraint.constraints(withVisualFormat: "H:[floatButton]-\(floatButtonBottomPadding)-|", options: NSLayoutConstraint.FormatOptions.alignAllCenterX, metrics: nil, views: views)
    self.parentView.addConstraints(trailingSpacing)
    self.parentView.addConstraints(bottomSpacing)
}

第二件事是设备是纵向的,单击按钮并显示菜单列表,将设备旋转到横向然后菜单列表消失但实际上 blurVisualEffect 和菜单列表仍然存在但是当设备时菜单列表不更新 y 位置旋转。

My solution here is add constraint between blurVisualEffect vs contentView , contentView vs parentView. I also add constraint for each menu item to contentView

查看 my repo 了解更多更新详情。

如果有任何问题请告诉我。

希望对您有所帮助!