Access objects in ViewController from a different

2019-09-12 00:08发布

问题:

I have a ViewController with a UIView named myView and a UIImageView named myImageView. In the code below, I have a class named viewLine which is attached to the UIView named myView.

The trouble I am having is when touchesEnded is called, I want to change the alpha of myImageView inside the ViewController. When I try this, no changes to the the alpha of myImageView occur.

(Alternatively, when I try to achieve this by moving the viewLine class into the main ViewController, the following errors present - override func drawRect(rect: CGRect) - Method does not override any method from its superclass and self.setNeedsDisplay() - Value of type 'ViewController' has no member 'setNeedsDisplay’.)

Questions:

1 - How do I modify the code in the class named viewLine to access other UI objects or functions on the ViewController storyboard such as myImageView? i.e. How do I change the alpha of myImageView from the class named viewLine after touchesEnded is called?

2 - I have a slider sliderLineSize in the ViewController and a variable lineSize in the ViewController. The UISlider sliderLineSize changes lineSize. However, lineSize is used in the drawRect section of the viewLine class. How do I pass or make accessible the variable set in the ViewController in the class?

(3 - How do instead incorporate the viewLine class into the main ViewController?)

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var myView: UIView!
    @IBOutlet weak var myImageView: UIImageView!

    var lineSize: Int = 1

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
        myImageView.alpha = 0.5
    }

    @IBAction func sliderLineSize(sender: UISlider) {
        lineSize = Int(sender.value)
    }

}

class viewLine: UIView {

    let path=UIBezierPath()
    var incrementalImage:UIImage?
    var previousPoint:CGPoint = CGPoint.zero
    var strokeColor:UIColor?

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

    override func drawRect(rect: CGRect) {
            incrementalImage?.drawInRect(rect)
            path.lineWidth = lineSize
            path.stroke()
    }

    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
        let touch: AnyObject? = touches.first
        let currentPoint = touch!.locationInView(self)
        path.moveToPoint(currentPoint)
        previousPoint=currentPoint
        self.setNeedsDisplay()
    }

    override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
        let touch: AnyObject? = touches.first
        let currentPoint = touch!.locationInView(self)
        let midPoint = self.midPoint(previousPoint, p1: currentPoint)
        path.addQuadCurveToPoint(midPoint,controlPoint: previousPoint)
        previousPoint=currentPoint
        path.moveToPoint(midPoint)
        self.setNeedsDisplay()
    }

    override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
        self.drawBitmap()
        self.setNeedsDisplay()
        path.removeAllPoints()
    }

    func midPoint(p0:CGPoint,p1:CGPoint)->CGPoint {
        let x=(p0.x+p1.x)/2
        let y=(p0.y+p1.y)/2
        return CGPoint(x: x, y: y)
    }

    func drawBitmap() {
        UIGraphicsBeginImageContextWithOptions(self.bounds.size, true, 1)
        strokeColor?.setStroke()
        if((incrementalImage) == nil){
            let rectPath:UIBezierPath = UIBezierPath(rect: self.bounds)
            UIColor.whiteColor().setFill()
            rectPath.fill()
        }
        incrementalImage?.drawAtPoint(CGPointZero)
        path.stroke()
        incrementalImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
    }

}

回答1:

I looked up about how to reference a ViewController from its subview and the best bet I found was a StackOverflow answer saying "Following the MVC pattern, a ViewController knows about its Views, but the View shouldn't know about the ViewController. Instead you should declare delegate protocol" (Source: Swift - How to pass a view controller's reference to a sub UIView class?)

So, I though the solution would be to write a custom delegate protocol and make the ViewController abide by it. The code in the aforementioned answer is a bit outdated to write the delegate protocol, but the beginning of the question in Pure Swift class conforming to protocol with static method - issue with upcasting shows how to write a protocol properly that works in my Xcode 7.2.

My storyboard has a diagram like this:

  • View
    • My View (linked to your code)
    • My Image View (background is a static image I chose)

I renamed the class viewLine to ViewLineUIView as I thought it would be a more descriptive name of the underlying superclass. The code is below.

Code:

import UIKit

protocol ViewLineUIViewDelegate: class {
    func onTouchesEnded(viewLine: ViewLineUIView)
}

class ViewController: UIViewController, ViewLineUIViewDelegate {

    @IBOutlet weak var myView: UIView!
    @IBOutlet weak var myImageView: UIImageView!
    @IBOutlet weak var sliderValue: UISlider!

    var viewLineDelegate: ViewLineUIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        viewLineDelegate = myView as? ViewLineUIView
        viewLineDelegate!.delegate = self

        //update lineWidth to initial value of slider
        updateLineWidth()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    func onTouchesEnded(viewLine: ViewLineUIView) {
        myImageView!.alpha = 0.5
    }

    //Updating lineWidth!
    @IBAction func onSliderChange(sender: UISlider) {
        updateLineWidth()

    }

    func updateLineWidth() {
        let drawView = myView as! ViewLineUIView
        //you can change this "10" value to your max lineWidth
        drawView.path.lineWidth = CGFloat(sliderValue.value*10)
        print("New value is: " + String(drawView.path.lineWidth))
    }


}

class ViewLineUIView: UIView {

    weak var delegate: ViewLineUIViewDelegate?

    let path=UIBezierPath()
    var incrementalImage:UIImage?
    var previousPoint:CGPoint = CGPoint.zero
    var strokeColor:UIColor?

    required init?(coder aDecoder: NSCoder) {
        //Initialize lineWidth so that compiler doesn't complain
        super.init(coder: aDecoder)
    }

    override func drawRect(rect: CGRect) {
        incrementalImage?.drawInRect(rect)
        path.stroke()
    }

    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
        let touch: AnyObject? = touches.first
        let currentPoint = touch!.locationInView(self)
        path.moveToPoint(currentPoint)
        previousPoint=currentPoint
        self.setNeedsDisplay()
    }

    override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
        let touch: AnyObject? = touches.first
        let currentPoint = touch!.locationInView(self)
        let midPoint = self.midPoint(previousPoint, p1: currentPoint)
        path.addQuadCurveToPoint(midPoint,controlPoint: previousPoint)
        previousPoint=currentPoint
        path.moveToPoint(midPoint)
        self.setNeedsDisplay()
    }

    override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
        self.drawBitmap()
        self.setNeedsDisplay()
        path.removeAllPoints()

        delegate?.onTouchesEnded(self)
    }

    func midPoint(p0:CGPoint,p1:CGPoint)->CGPoint {
        let x=(p0.x+p1.x)/2
        let y=(p0.y+p1.y)/2
        return CGPoint(x: x, y: y)
    }

    func drawBitmap() {
        UIGraphicsBeginImageContextWithOptions(self.bounds.size, true, 1)
        strokeColor?.setStroke()
        if((incrementalImage) == nil){
            let rectPath:UIBezierPath = UIBezierPath(rect: self.bounds)
            UIColor.whiteColor().setFill()
            rectPath.fill()
        }
        incrementalImage?.drawAtPoint(CGPointZero)
        path.stroke()
        incrementalImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
    }

}

As you can see, editing the lineWidth of the path is straightforward and you don't need to create another delegate protocol. The ViewController can access its subviews directly. You should just be aware that a cast is needed.