gpt4 book ai didi

ios - UICollectionViewCell 延迟重绘自定义 UIView 子层

转载 作者:行者123 更新时间:2023-11-29 11:45:27 31 4
gpt4 key购买 nike

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

问题在于,当我滚动并且单元格被重新使用时,饼图会在这些单元格中持续存在一小段时间,然后才会重新绘制。

这里是重现这个的方法:

  1. 创建一个新的单 View 项目
  2. 将下面的代码复制粘贴到 ViewController.swift 和 Main.storyboard
  3. 构建并运行
  4. 向下滚动:您会看到一堆彩色圆点。再滚动一些,您应该会看到闪烁的点。

你可能会问的事情:

  • 这是一个简化的“日历”,包含 10 个月和 30 天(单元格),只有第 2 个月有点来展示问题。
  • 我将 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()
}
}

主要 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,不太确定这是否会改变任何东西。我需要确保此解决方案可以扩展到该项目的非简化版本。

最佳答案

Apple Docs: API Reference

setNeedsDisplay()

You should use this method to request that a view to be redrawn only when the content or appearance of the view change. If you simply change the geometry of the view, the view is typically not redrawn. Instead, its existing content is adjusted based on the value in the view’s contentMode property. Redisplaying the existing content improves performance by avoiding the need to redraw content that has not changed.

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

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

31 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com