问题
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)
来源:https://stackoverflow.com/questions/54634147/creating-reusable-view-in-swift-and-adding-a-completion-handler