George Nick Gorzynski
George Nick Gorzynski

Reputation: 87

CAShapeLayer not showing on screen

I've got a UICollectionViewCell that has a UIView inside. When dequeueing the cell, I initialise it with the necessary data. After the data is initialised, inside the UIView, there is some drawing that is meant to be displayed. However, nothing appears.

Initially, this was all inside UICollectionView cellForItemAt method and drew the lines, but it should've been inside a UIView so I subclassed it and placed the code in there. This is where it stopped working.

I've tried overriding the draw method, calling my methods with the drawing inside init, and calling them from the cell. I get the print statements showing in the console, but no drawing on screen.

extension ChooseRouteViewController: UICollectionViewDelegate, UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Route", for: indexPath) as! PotentialRouteCell

        cell.routeOutline = RouteOverview(frame: CGRect(x: 73 + 4, y: 8, width: (cell.bounds.width - 83), height: 98), route: instructions)

        return cell
    }

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

class RouteOverview: UIView {

    init(frame: CGRect, route: [Instruction]) {
        super.init(frame: frame)

        getOutline(from: route)
    }

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

    private struct Overview {
         let fromStation: Station
         let toStation: Station
         let lineName: String

         init(from: Station, to: Station, line: String) {
              self.fromStation = from
              self.toStation = to
              self.lineName = line
         }
    }

    private var overview: [Overview] = []
    private var keyStations: [String] = []
    private var linesToDraw: Int = 0
    private var linesDrawn: [CAShapeLayer] = []

    private var lineX: CGFloat = 24.0
    private var lineY: CGFloat {
         return self.bounds.height / 2
    }

    private var lineLength: CGFloat {
        return (self.bounds.width - 48) / CGFloat(overview.count)
    }

    private func getOutline(from route: [Instruction]) {
        for instruction in route {
            if instruction.type == .route {
                 let instructionOverview = Overview(from: instruction.route.first!.station, to: instruction.route.last!.station, line: instruction.line)
                 overview.append(instructionOverview)
            }
        }
        linesToDraw = overview.count

        print("\n Debug: There are \(linesToDraw) lines to draw. Each line is \(lineLength) points long.")
        drawLine(fromPoint: CGPoint(x: lineX, y: lineY), toPoint: CGPoint(x: lineX + lineLength, y: lineY), lineName: overview[linesDrawn.count].lineName)
    }

    private func drawLine(fromPoint start: CGPoint, toPoint end: CGPoint, lineName: String) {
        lineX += lineLength

        let line = CAShapeLayer()
        let path = UIBezierPath()

        path.move(to: start)
        path.addLine(to: end)

        line.lineWidth = 8
        line.lineCap = .round
        line.strokeColor = UIColor(named: lineName)!.cgColor
        line.fillColor = UIColor(named: lineName)!.cgColor
        line.path = path.cgPath

        self.layer.insertSublayer(line, at: 0)

        linesDrawn.append(line)
        linesToDraw -= 1

        if linesToDraw >= 0 {
             addDetail(at: end, lineName: lineName)
        }
    }

    func addDetail(at point: CGPoint, lineName: String) {
        if linesToDraw != 0 {
            let circle = CAShapeLayer()
            let path = UIBezierPath(arcCenter: point, radius: 8, startAngle: 0, endAngle: CGFloat(Double.pi * 2), clockwise: true)

            circle.lineWidth = 6
            circle.strokeColor = UIColor.black.cgColor
            circle.fillColor = UIColor.black.cgColor
            circle.path = path.cgPath

            let innerCircle = CAShapeLayer()
            let innerPath = UIBezierPath(arcCenter: point, radius: 4, startAngle: 0, endAngle: CGFloat(Double.pi * 2), clockwise: true)

            innerCircle.lineWidth = 6
            innerCircle.strokeColor = UIColor.white.cgColor
            innerCircle.fillColor = UIColor.white.cgColor
            innerCircle.path = innerPath.cgPath

            self.layer.addSublayer(circle)
            self.layer.insertSublayer(innerCircle, above: circle)
        }

        let lineNameLabel = RoundLabel(frame: CGRect(x: point.x - (lineLength / 2) - 18, y: point.y + 16, width: 40, height: 24))

        lineNameLabel.cornerRadius = 5.0
        lineNameLabel.text = String(lineName.prefix(3))
        lineNameLabel.textAlignment = .center
        lineNameLabel.font = UIFont(name: "London Tube", size: 15.0)
        lineNameLabel.backgroundColor = UIColor(named: lineName)!
        lineNameLabel.textColor = .white
        lineNameLabel.clipsToBounds = true

        self.addSubview(lineNameLabel)
        self.setNeedsLayout()
        self.setNeedsDisplay()

        if linesToDraw >= 1 {
            drawLine(fromPoint: CGPoint(x: point.x, y: lineY), toPoint: CGPoint(x: point.x + lineLength, y: lineY), lineName: overview[linesDrawn.count].lineName)
        }
    }
}

It should draw a line for each route instruction, in the color of the train line name. Where two lines meet, there should be a connector blob as seen on a London Underground tube map. There should also be an abbreviated train line name label the drawn line.

Upvotes: 0

Views: 632

Answers (1)

Rob
Rob

Reputation: 437552

In cellForItemAt you set routineOutline to your RouteOverview. So you're saving a reference to your custom view, but I don't see you adding the RouteOverview to the view hierarchy at any point. (You're adding shape layers to RouteOverview, but never adding the RouteOverview, itself.) You need to call addSubview at some point.

Upvotes: 1

Related Questions