Creating reusable View in Swift and adding a compl

2019-08-29 01:45发布

问题:

I created a sliderView, and added a UIPanGesture and I created a seperate UIView to handle this slider. I want to be able to call this SliderView in different ViewControllers where I need it but when I try to run my code IT crashed.I also would want to pass a completion handler to handle events in the ViewCOntrollers where I would be needing it. I am trying to follow the DRY process by creating one UIView and using it in mutiple screen. Below is my code so far

class TripView: UIView {

    var shouldSetupConstraints = true
    var startingFrame: CGRect?

    var sliderView: UIView!
    var sliderImage: UIImageView!

    let screenSize = UIScreen.main.bounds

    override init(frame: CGRect){
        super.init(frame: frame)
        swipeFunc()
        sliderView = UIImageView(frame: CGRect.zero)
        sliderView.backgroundColor = UIColor.green

        sliderView.autoSetDimension(.height, toSize: screenSize.width / 6)

        self.addSubview(sliderView)

        sliderImage = UIImageView(frame: CGRect.zero)
        sliderImage.backgroundColor = UIColor.clear
        sliderImage.image = UIImage(named: "icons8-double_right_filled.png")
        sliderImage.contentMode = .scaleAspectFit

        sliderImage.autoSetDimension(.width, toSize: screenSize.width / 6)
        sliderImage.autoSetDimension(.height, toSize: screenSize.width / 6)

        self.addSubview(sliderImage)


    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    override func updateConstraints() {
        if(shouldSetupConstraints) {

            sliderView.autoPinEdgesToSuperviewSafeArea(with: UIEdgeInsets.zero, excludingEdge: .bottom)
            sliderImage.autoPinEdge(toSuperviewEdge: .left)
            sliderImage.autoPinEdge(.bottom, to: .bottom, of: sliderView, withOffset: 0.0)

            shouldSetupConstraints = false
        }

        super.updateConstraints()
    }
}

extension TripView {

    private func swipeFunc() {

        let swipeGesture = UIPanGestureRecognizer(target: self, action: #selector(acknowledgeSwiped(sender:)))
        sliderImage.addGestureRecognizer(swipeGesture)
        sliderImage.isUserInteractionEnabled = true
        swipeGesture.delegate = self as? UIGestureRecognizerDelegate
    }

    @objc func acknowledgeSwiped(sender: UIPanGestureRecognizer) {
        if let sliderView = sender.view {
            let translation = sender.translation(in: sliderView)
            switch sender.state {
            case .began:
                startingFrame = sliderImage.frame
                fallthrough
            case .changed:
                if let startFrame = startingFrame {

                    var movex = translation.x
                    if movex < -startFrame.origin.x { movex = -startFrame.origin.x }

                    let xMax = sliderView.frame.width - startFrame.origin.x - startFrame.width
                    if movex > xMax {
                        movex = xMax

      // I WANT TO PASS A COMPLETION HANDLER HERE. TO BE ABLE TO HANDLE OTHER     EVENTS IN VIEWCONTROLLERS

                    }

                    var movey = translation.y
                    if movey < -startFrame.origin.y { movey = -startFrame.origin.y }

                    let yMax = sliderView.frame.height - startFrame.origin.y - startFrame.height
                    if movey > yMax {
                        movey = yMax

                    }

                    sliderView.transform = CGAffineTransform(translationX: movex, y: movey)
                }
            default: // .ended and others:
                UIView.animate(withDuration: 0.1, animations: {
                    sliderView.transform = CGAffineTransform.identity
                })
            }
        }
    }

}

回答1:

You are calling swipeFunc which accesses sliderImage before sliderImage is initialized.

You have to move swipeFunc() to the end of your init method.

Actually, there is no need to use implicitly unwrapped optionals. You can prevent the crashes by using a non-optional:

let sliderView: UIView = UIImageView(frame: CGRect.zero)
let sliderImage: UIImageView = UIImageView(frame: CGRect.zero)

Since you actually have two initializers, you should probably init everything in both:

let sliderView: UIView = UIImageView(frame: CGRect.zero)
let sliderImage: UIImageView = UIImageView(frame: CGRect.zero)

override init(frame: CGRect){
    super.init(frame: frame)

    commonInit()
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)

    commonInit()
}

private func commonInit() {
    sliderView.backgroundColor = UIColor.green
    sliderView.autoSetDimension(.height, toSize: screenSize.width / 6)

    self.addSubview(sliderView)

    sliderImage.backgroundColor = UIColor.clear
    sliderImage.image = UIImage(named: "icons8-double_right_filled.png")
    sliderImage.contentMode = .scaleAspectFit

    sliderImage.autoSetDimension(.width, toSize: screenSize.width / 6)
    sliderImage.autoSetDimension(.height, toSize: screenSize.width / 6)

    self.addSubview(sliderImage)

    swipeFunc()
}

Regarding a callback, simply declare a property:

class TripView: UIView {
    var onChange: (() -> Void)?
}

and on every change call:

onChange?()

In your controller you can then:

var tripView: TripView = ...
tripView.onChange = {
  // handle the event
}


回答2:

You can add a completion handler as an internal variable, or pass one in the initialiser.

e.g:

class TripView: UIView {
    typealias Callback = () -> ()
    var callback: Callback

    ...
}

Then where you want to trigger the callback, simply call:

callback()

Or you could define a delegate

protocol TripViewDelegate {
    func didDoSomething(for view: TripView)
}

class TripView: UIView {
    weak var tripViewDelegate: TripViewDelegate?
    // ...
}

And then call

tripViewDelegate?.didDoSomething(for: self)