简体   繁体   中英

Can't draw a line on a UIView

I have a UIView that contains two UITextfields, I want to create a 1.0 pixel width separator between the two, but I just can't get it to work. I tried overriding the draw func, but it didn't work, I also tried using bezierPath and shapeLayer, but I don't have the UIView's frame, so the bezierPath can't be drawn.

This is the draw override I have:

override func draw(_ rect: CGRect) {
    super.draw(rect)

    if let context = UIGraphicsGetCurrentContext() {
        context.setStrokeColor(UIColor.black.cgColor)
        context.setLineWidth(1)
        context.move(to: CGPoint(x: bounds.width / 2, y: 0))
        context.addLine(to: CGPoint(x: bounds.width / 2, y: bounds.height))
        context.strokePath()
    }
}

This is the bezierPath func:

private func drawSeparator() {
    let path = UIBezierPath()
    path.addLine(to: CGPoint(x: bounds.width / 2, y: 0))
    path.move(to: CGPoint(x: bounds.width / 2, y: self.bounds.height))

    let shape = CAShapeLayer()
    shape.path = path.cgPath
    shape.strokeColor = UIColor.appGray.cgColor
    shape.lineWidth = 1.0
    shape.fillColor = UIColor.appGray.cgColor

    self.layer.addSublayer(shape)
}

I also tried moving the path variable to the class level, and using it as lazy var to try and get the frame/bounds that way, but it also didn't work.

I'm trying to create a simple, vertical separator.

It's unclear what you're hoping to do, but here's a complete working version based directly on your code:

class MyView: UIView {
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.isOpaque = false
    }
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    override func draw(_ rect: CGRect) {
        super.draw(rect)
        if let context = UIGraphicsGetCurrentContext() {
            context.setStrokeColor(UIColor.black.cgColor)
            context.setLineWidth(1)
            context.move(to: CGPoint(x: bounds.width / 2, y: 0))
            context.addLine(to: CGPoint(x: bounds.width / 2, y: bounds.height))
            context.strokePath()
        }
    }
}

class ViewController: UIViewController {
    let myView = MyView()
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        if myView.superview == nil {
            self.view.addSubview(myView)
        }
        myView.frame = self.view.bounds
        myView.setNeedsDisplay()
    }
}

That looks like this:

在此处输入图像描述

Okay, so now that you've seen a working example, adapt it to your own situation and you're all set.

While there are refinements I would suggest, your draw(:_) rendition works fine. (Your shape layer rendition is wrong... you want to decouple the creation of the shape layer and the updating of the path; but let's set that aside.)

If you're not seeing your view's separator line rendered in your draw(_:) rendition, the problem rests elsewhere. Try inserting log statement in the draw method and make sure you're hitting this code like you think it should and that the bounds is what you think it should be. Or use the view debugger to confirm the base class is set correctly and that the frame is what you think it is, etc.


I might suggest using midX , minY and maxY . Personally, I'd also not drop into CoreGraphics unless necessary:

@IBDesignable
class VerticalSplitView: UIView {
    override func draw(_ rect: CGRect) {
        super.draw(rect)

        let path = UIBezierPath()
        path.move(to: CGPoint(x: bounds.midX, y: bounds.minY))
        path.addLine(to: CGPoint(x: bounds.midX, y: bounds.maxY))
        path.lineWidth = 1

        UIColor.black.setStroke()
        path.stroke()
    }
}

By the way, if you use this draw(_:) approach, make sure you set your view's content mode to be redraw . In the absence of that, you can get strange behavior (eg if the constraints link the width of this view to its superview and user rotates device, it's going to stretch the width of this vertical line).


Personally, I like to get out of manual drawing and hand that off to a CAShapeLayer . That way I don't have to worry about the contentMode of the view and I offload the drawing to Apple's code (which undoubtedly has optimizations that we don't consider here).

As I mentioned, you have to decouple the creation of the shape layer (to be done during initialization) from the updating of its path (which should be done in layoutSubviews ).

Personally, I might have a view whose backing layer is a CAShapeLayer :

@IBDesignable
class VerticalShapeLayer: UIView {
    override class var layerClass: AnyClass { return CAShapeLayer.self }
    lazy var shapeLayer: CAShapeLayer = layer as! CAShapeLayer
    
    override func layoutSubviews() {
        super.layoutSubviews()

        let path = UIBezierPath()
        path.move(to: CGPoint(x: bounds.midX, y: bounds.minY))
        path.addLine(to: CGPoint(x: bounds.midX, y: bounds.maxY))

        shapeLayer.strokeColor = UIColor { traitCollection in
            switch traitCollection.userInterfaceStyle {
            case .dark: return .white
            default:    return .black
            }
        }.cgColor
        shapeLayer.lineWidth = 1
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.path = path.cgPath
    }
}

Also, as a refinement, I would set the color of the stroke based upon whether the device is in dark mode or not. That obviously presumes that you set the background colors accordingly, too. Do whatever you want regarding the stroke color.


One final suggestion once you have your immediate problem behind you: If you're using NIBs or storyboards, I'd designate this view class as @IBDesignable . That way I can see it right in Interface Builder, eliminating a lot of these questions. We can see it rendered during the design process, removing ambiguity about whether the base class was set right, that the frame is correct, etc.

It's certainly not necessary, but @IBDesignable helps identify issues during the design process.

For the line you can also use UIView between the textField as a separator(set width of UIView).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM