我想在“ Activity ”应用中显示类似于历史记录的内容,但是出于这个问题,这是一个简单的饼图,而不是3个环。
我创建了一个自定义UIView并使用draw(ctx :)绘制饼图。

麻烦在于,当我滚动并重新使用单元格时,饼图会在这些单元格中保留一会儿,然后重新绘制。

重现此内容的方法如下:

  • 创建一个新的单视图项目
  • 复制将以下代码粘贴到ViewController.swift和Main.storyboard中
  • 构建并运行
  • 向下滚动:您会看到一堆彩色的点。滚动一些,您将看到闪烁的点。

  • 您可能会问的事情:
  • 这是10个月的简化“日历”,其中包含30天(单元格),只有第二个月才有点来显示该问题。
  • 我将pieLayer添加为UIView层的子层,而不是直接使用该层,因为在我的项目中,我不仅拥有一个自定义层

  • ViewController.swift
    class ViewController: UICollectionViewController {
       override func numberOfSections(in collectionView: UICollectionView) -> Int {
            return 10
        }
    
        override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
            return 30
        }
    
       override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "DayCell", for: indexPath) as! RingCell
            let ring = cell.ring!
            ring.pieLayer.radius = 15
            ring.pieLayer.maxValue = 30
            if indexPath.section == 2 {
                ring.pieLayer.value = CGFloat(indexPath.row)
                ring.pieLayer.segmentColor = (indexPath.row % 2 == 0 ? UIColor.green.cgColor : UIColor.red.cgColor)
            } else {
                ring.pieLayer.value = 0
                ring.pieLayer.segmentColor = UIColor.clear.cgColor
            }
            ring.pieLayer.setNeedsDisplay()
            return cell
        }
    }
    
    class RingCell: UICollectionViewCell {
        @IBOutlet weak var ring: PieView!
    
        override func prepareForReuse() {
            super.prepareForReuse()
            ring.pieLayer.value = 0
            ring.pieLayer.segmentColor = UIColor.clear.cgColor
            ring.pieLayer.setNeedsDisplay()
        }
    }
    
    open class PieView: UIView {
    
        // MARK: Initializers
    
        public override init(frame: CGRect) {
            super.init(frame: frame)
            initLayers()
        }
    
        public required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            initLayers()
        }
    
        // MARK: Internal initializers
    
        var pieLayer: ProgressPieLayer!
    
        internal func initLayers() {
            pieLayer = ProgressPieLayer(centeredIn: layer.bounds)
            rasterizeToScale(pieLayer)
            layer.addSublayer(pieLayer)
            pieLayer.setNeedsDisplay()
        }
    
        private func rasterizeToScale(_ layer: CALayer) {
            layer.contentsScale = UIScreen.main.scale
            layer.shouldRasterize = true
            layer.rasterizationScale = UIScreen.main.scale * 2
        }
    }
    
    private extension CGFloat {
        var toRads: CGFloat { return self * CGFloat.pi / 180 }
    }
    
    internal class ProgressPieLayer: CAShapeLayer {
        @NSManaged var value: CGFloat
        @NSManaged var maxValue: CGFloat
        @NSManaged var radius: CGFloat
        @NSManaged var segmentColor: CGColor
    
        convenience init(centeredIn bounds: CGRect,
                         radius: CGFloat = 15,
                         color: CGColor = UIColor.clear.cgColor,
                         value: CGFloat = 100,
                         maxValue: CGFloat = 100) {
            self.init()
            self.bounds = bounds
            self.position = CGPoint(x: bounds.midX, y: bounds.midY)
            self.value = value
            self.maxValue = maxValue
            self.radius = radius
            self.segmentColor = color
        }
    
        override func draw(in ctx: CGContext) {
            super.draw(in: ctx)
            let shiftedStartAngle: CGFloat = -90 // start on top
            let center = CGPoint(x: bounds.midX, y: bounds.midY)
            let angle = 360 / maxValue * value + shiftedStartAngle
    
            ctx.move(to: center)
            ctx.addArc(center: center,
                       radius: radius,
                       startAngle: shiftedStartAngle.toRads,
                       endAngle: angle.toRads,
                       clockwise: false)
            ctx.setFillColor(segmentColor)
            ctx.fillPath()
        }
    }
    

    主板
    <?xml version="1.0" encoding="UTF-8"?>
    <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12120" systemVersion="16F73" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="NK3-ad-iUE">
        <device id="retina4_7" orientation="portrait">
            <adaptation id="fullscreen"/>
        </device>
        <dependencies>
            <deployment identifier="iOS"/>
            <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12088"/>
            <capability name="Constraints to layout margins" minToolsVersion="6.0"/>
            <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
        </dependencies>
        <scenes>
            <!--View Controller-->
            <scene sceneID="NFp-0o-M02">
                <objects>
                    <collectionViewController id="NK3-ad-iUE" customClass="ViewController" customModule="UICN" customModuleProvider="target" sceneMemberID="viewController">
                        <collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" id="Sy5-uf-jPK">
                            <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                            <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                            <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
                            <collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="0.0" minimumInteritemSpacing="0.0" id="fkD-3N-K4T">
                                <size key="itemSize" width="50" height="50"/>
                                <size key="headerReferenceSize" width="0.0" height="0.0"/>
                                <size key="footerReferenceSize" width="0.0" height="0.0"/>
                                <inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
                            </collectionViewFlowLayout>
                            <cells>
                                <collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="DayCell" id="CXc-tU-7nQ" customClass="RingCell" customModule="UICN" customModuleProvider="target">
                                    <rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
                                    <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
                                    <view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
                                        <rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
                                        <autoresizingMask key="autoresizingMask"/>
                                        <subviews>
                                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Ic6-ea-Qzy" userLabel="Pie" customClass="PieView" customModule="UICN" customModuleProvider="target">
                                                <rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
                                                <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
                                            </view>
                                        </subviews>
                                    </view>
                                    <constraints>
                                        <constraint firstAttribute="trailingMargin" secondItem="Ic6-ea-Qzy" secondAttribute="trailing" constant="-8" id="9fj-SE-D1e"/>
                                        <constraint firstItem="Ic6-ea-Qzy" firstAttribute="top" secondItem="CXc-tU-7nQ" secondAttribute="topMargin" constant="-8" id="Hnv-yr-EBN"/>
                                        <constraint firstItem="Ic6-ea-Qzy" firstAttribute="leading" secondItem="CXc-tU-7nQ" secondAttribute="leadingMargin" constant="-8" id="I4E-ZD-JZf"/>
                                        <constraint firstAttribute="bottomMargin" secondItem="Ic6-ea-Qzy" secondAttribute="bottom" constant="-8" id="XOW-ao-t0L"/>
                                    </constraints>
                                    <connections>
                                        <outlet property="ring" destination="Ic6-ea-Qzy" id="ZoZ-ok-TLK"/>
                                    </connections>
                                </collectionViewCell>
                            </cells>
                            <connections>
                                <outlet property="dataSource" destination="NK3-ad-iUE" id="nAW-La-2EK"/>
                                <outlet property="delegate" destination="NK3-ad-iUE" id="YCh-0p-7gX"/>
                            </connections>
                        </collectionView>
                    </collectionViewController>
                    <placeholder placeholderIdentifier="IBFirstResponder" id="6r8-g7-Adg" userLabel="First Responder" sceneMemberID="firstResponder"/>
                </objects>
                <point key="canvasLocation" x="-100" y="214.54272863568218"/>
            </scene>
        </scenes>
    </document>
    

    编辑

    我可能已经找到了解决方案,我在ProgressPieLayer中创建了一个drawPie方法。
    internal class ProgressPieLayer: CAShapeLayer {
        @NSManaged var value: CGFloat
        @NSManaged var maxValue: CGFloat
        @NSManaged var radius: CGFloat
        @NSManaged var segmentColor: CGColor
    
        convenience init(centeredIn bounds: CGRect,
                         radius: CGFloat = 15,
                         color: CGColor = UIColor.clear.cgColor,
                         value: CGFloat = 100,
                         maxValue: CGFloat = 100) {
            self.init()
            self.bounds = bounds
            self.position = CGPoint(x: bounds.midX, y: bounds.midY)
            self.value = value
            self.maxValue = maxValue
            self.radius = radius
            self.segmentColor = color
        }
    
        func drawPie() {
            let shiftedStartAngle: CGFloat = -90 // start on top
            let center = CGPoint(x: bounds.midX, y: bounds.midY)
            let angle = 360 / maxValue * value + shiftedStartAngle
            let piePath = UIBezierPath(arcCenter: center, radius: radius, startAngle: shiftedStartAngle.toRads, endAngle: angle.toRads, clockwise: false)
            piePath.addLine(to: center)
            self.path = piePath.cgPath
            self.fillColor = segmentColor
        }
    }
    

    我打电话
    ring.pieLayer.drawPie()
    

    在UICollectionViewCell#prepareForReuse和collectionView(_ collectionView:cellForItemAt :)中,它可以正常工作

    我正在使用UIBezierPath而不是CGContext,不太确定是否会发生任何变化。我需要确保可以将该解决方案扩展到非简化版的projet。

    最佳答案

    Apple Docs:API参考

    setNeedsDisplay()

    您应该使用此方法来请求仅重绘视图
    当视图的内容或外观发生变化时。如果你只是
    更改视图的几何形状后,通常不会重绘视图。
    相反,其现有内容将根据
    查看的contentMode属性。重新显示现有内容
    通过避免重绘具有以下内容的内容来提高性能
    没有改变。

    基本上,setNeedsDisplay()在下一个绘制周期中从头开始重新绘制所有内容。因此,理想的方法是只创建一次UI元素实例,并在需要时更新框架或路径。它不会完全重绘所有内容,因此效率很高。

    关于ios - UICollectionViewCell延迟重绘自定义UIView子图层,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/44156516/

    10-12 14:46