我想在“活动”应用中显示类似于历史记录的内容,但为了这个问题,它是一个简单的饼图,而不是3个环。 我创建了一个自定义的UIView并使用draw(在ctx :)中绘制饼图。
问题在于,当我滚动并重复使用单元格时,饼图会在重新绘制之前在这些单元格中保留一小段时间。
以下是如何重现这一点:
你可能会问的事情:
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的非简化版本。
答案 0 :(得分:1)
Apple Docs:API参考
setNeedsDisplay()
您应该使用此方法请求仅重绘视图 当视图的内容或外观发生变化时。如果你只是 更改视图的几何图形,通常不会重绘视图。 而是根据其中的值调整其现有内容 view的contentMode属性。重新显示现有内容 通过避免重绘具有的内容的需要来提高性能 没有改变。
基本上setNeedsDisplay()在下一个绘图周期中从头开始重绘所有内容。因此,理想的方法是只创建一次UI元素的实例,并在需要时更新框架或路径。它没有完全重绘所有内容,因此效率很高。