UICollectionViewCell重新绘制延迟

时间:2017-05-24 11:04:19

标签: ios uicollectionviewcell calayer

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

问题在于,当我滚动并重复使用单元格时,饼图会在重新绘制之前在这些单元格中保留一小段时间。

以下是如何重现这一点:

  1. 创建新的单一视图项目
  2. 在ViewController.swift和Main.storyboard
  3. 中复制粘贴以下代码
  4. 构建&运行
  5. 向下滚动:你会看到一堆彩色圆点。滚动一些,你应该看到闪烁的点。
  6. 你可能会问的事情:

    • 这是一个简化的"日历" 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()
        }
    }
    

    Main.Storyboard

    <?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的非简化版本。

1 个答案:

答案 0 :(得分:1)

  

Apple Docs:API参考

     

setNeedsDisplay()

     

您应该使用此方法请求仅重绘视图   当视图的内容或外观发生变化时。如果你只是   更改视图的几何图形,通常不会重绘视图。   而是根据其中的值调整其现有内容   view的contentMode属性。重新显示现有内容   通过避免重绘具有的内容的需要来提高性能   没有改变。

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