我在自定义 UITableViewCell 中有一个 UISlider。 当我查看
awakeFromNib
中滑块的大小时,.frame
属性显示的是在故事板中设置的滑块大小,而不是视图出现时绘制的最终大小。
我原以为所有设置都是在
awakeFromNib
中完成的,但滑块的大小似乎在 awakeFromNib 和其最终外观之间发生了变化。
我在 2015 年发现了一个类似的问题,该问题已发布答案,但实际上并未得到解决。
我在2016年也发现了一个类似的问题,但这个问题似乎不适用于我的情况。
Swift UITableViewCell 子视图布局更新延迟
我添加了故事板中设置的约束的屏幕截图。
我们不知道单元格(及其 UI 组件)的大小,直到
layoutSubviews()
因此,假设您将箭头位置设置为百分比,请按照以下方式在单元类中实现
layoutSubviews()
:
override func layoutSubviews() {
super.layoutSubviews()
// the thumb "circle" extends to the bounds / frame of the slider
// so, this is how we get the
// thumb center-to-center
// when value is 0 or 1.0
let trackRect = theSlider.trackRect(forBounds: theSlider.bounds)
let thumbRect = theSlider.thumbRect(forBounds: theSlider.bounds, trackRect: trackRect, value: 0.0)
let rangeWidth = theSlider.bounds.width - thumbRect.width
// Zero will be 1/2 of the width of the thumbRect
// minus 2 (because the thumb image is slightly offset from the thumb rect)
let xOffset = (thumbRect.width * 0.5) - 2.0
// create the arrow constraints if needed
if startConstraint == nil {
startConstraint = startArrow.centerXAnchor.constraint(equalTo: theSlider.leadingAnchor)
startConstraint.isActive = true
}
if endConstraint == nil {
endConstraint = endArrow.centerXAnchor.constraint(equalTo: theSlider.leadingAnchor)
endConstraint.isActive = true
}
// set arrow constraint constants
startConstraint.constant = rangeWidth * startTime + xOffset
endConstraint.constant = rangeWidth * endTime + xOffset
}
我假设所有行的滑块都有相同的“时间范围”,因此我们可以得到类似的结果(我将拇指色调设置为半透明,箭头 y 位置设置为这样我们可以看到对齐情况):
对于完整的示例(生成该输出),请使用此故事板:
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="22154" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="J6Q-B3-lSE">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22130"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Slider TableVC-->
<scene sceneID="yXs-sI-IdP">
<objects>
<tableViewController id="J6Q-B3-lSE" customClass="SliderTableVC" customModule="TmpTmp" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="-1" estimatedSectionHeaderHeight="-1" sectionFooterHeight="-1" estimatedSectionFooterHeight="-1" id="JRC-Ch-MFI">
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="sliderCell" rowHeight="233" id="SCF-ak-VtA" customClass="SliderCell" customModule="TmpTmp" customModuleProvider="target">
<rect key="frame" x="0.0" y="50" width="393" height="233"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="SCF-ak-VtA" id="OEa-iz-t0R">
<rect key="frame" x="0.0" y="0.0" width="393" height="233"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="55g-fd-LTL">
<rect key="frame" x="20" y="11.000000000000002" width="47" height="21.666666666666671"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="18"/>
<color key="textColor" red="0.016804177310000001" green="0.19835099580000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="m8D-IM-8fn">
<rect key="frame" x="327" y="11" width="46" height="23"/>
<fontDescription key="fontDescription" type="system" pointSize="19"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="iHQ-9O-6Vz">
<rect key="frame" x="20" y="33.666666666666664" width="65" height="28.666666666666664"/>
<string key="text">Label
Label Label</string>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="32b-om-4Ux">
<rect key="frame" x="178" y="41" width="37.333333333333343" height="18"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<color key="textColor" systemColor="systemRedColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" textAlignment="right" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="PaO-BC-b0I">
<rect key="frame" x="308" y="35" width="65" height="28.666666666666671"/>
<string key="text">Label
Label Label</string>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="1000" verticalHuggingPriority="251" horizontalCompressionResistancePriority="1000" text="Label" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="r3Y-fY-1x1">
<rect key="frame" x="20" y="114" width="26.333333333333329" height="12"/>
<fontDescription key="fontDescription" type="system" pointSize="10"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="1000" verticalHuggingPriority="251" horizontalCompressionResistancePriority="1000" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="B0s-gC-ToH">
<rect key="frame" x="346.66666666666669" y="114" width="26.333333333333314" height="12"/>
<fontDescription key="fontDescription" type="system" pointSize="10"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="0.5" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="5Oa-0J-dF1">
<rect key="frame" x="18" y="75" width="357" height="31"/>
<color key="thumbTintColor" red="0.0" green="0.97680455450000003" blue="0.0" alpha="0.25" colorSpace="custom" customColorSpace="sRGB"/>
</slider>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="arrowshape.up" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="bGx-Yf-Gjd">
<rect key="frame" x="139.33333333333334" y="93.333333333333329" width="20" height="19.666666666666671"/>
<color key="tintColor" systemColor="systemRedColor"/>
<constraints>
<constraint firstAttribute="width" constant="20" id="Bo0-t0-Url"/>
<constraint firstAttribute="width" secondItem="bGx-Yf-Gjd" secondAttribute="height" multiplier="1:1" id="Y0i-jC-1lI"/>
</constraints>
</imageView>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="arrowshape.up" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="1VL-rV-Hrb">
<rect key="frame" x="269.66666666666669" y="93.333333333333329" width="20" height="19.666666666666671"/>
<color key="tintColor" systemColor="systemRedColor"/>
<constraints>
<constraint firstAttribute="width" secondItem="1VL-rV-Hrb" secondAttribute="height" multiplier="1:1" id="Sqy-RN-XxK"/>
</constraints>
</imageView>
</subviews>
<constraints>
<constraint firstItem="5Oa-0J-dF1" firstAttribute="leading" secondItem="55g-fd-LTL" secondAttribute="leading" id="37d-l5-EIJ"/>
<constraint firstItem="r3Y-fY-1x1" firstAttribute="top" secondItem="bGx-Yf-Gjd" secondAttribute="bottom" constant="1" id="4FI-YR-2Wo"/>
<constraint firstItem="5Oa-0J-dF1" firstAttribute="top" secondItem="32b-om-4Ux" secondAttribute="bottom" constant="16" id="4HV-Pg-Nh8"/>
<constraint firstItem="B0s-gC-ToH" firstAttribute="top" secondItem="r3Y-fY-1x1" secondAttribute="top" id="7mH-Fq-0GS"/>
<constraint firstItem="1VL-rV-Hrb" firstAttribute="centerX" secondItem="5Oa-0J-dF1" secondAttribute="trailing" multiplier="0.75" priority="250" id="AOS-Da-kaA"/>
<constraint firstAttribute="bottomMargin" relation="greaterThanOrEqual" secondItem="r3Y-fY-1x1" secondAttribute="bottom" priority="999" id="DmE-Is-Qe0"/>
<constraint firstItem="55g-fd-LTL" firstAttribute="top" secondItem="OEa-iz-t0R" secondAttribute="topMargin" id="HRM-zZ-7BL"/>
<constraint firstItem="1VL-rV-Hrb" firstAttribute="width" secondItem="bGx-Yf-Gjd" secondAttribute="width" id="KKN-J1-SFy"/>
<constraint firstItem="PaO-BC-b0I" firstAttribute="trailing" secondItem="m8D-IM-8fn" secondAttribute="trailing" id="MHS-f4-WMg"/>
<constraint firstItem="r3Y-fY-1x1" firstAttribute="leading" secondItem="OEa-iz-t0R" secondAttribute="leadingMargin" id="MXt-DF-eMa"/>
<constraint firstItem="iHQ-9O-6Vz" firstAttribute="leading" secondItem="55g-fd-LTL" secondAttribute="leading" id="NnH-rl-Yoo"/>
<constraint firstItem="m8D-IM-8fn" firstAttribute="top" secondItem="OEa-iz-t0R" secondAttribute="topMargin" id="OBB-ee-MN9"/>
<constraint firstItem="PaO-BC-b0I" firstAttribute="top" secondItem="m8D-IM-8fn" secondAttribute="bottom" constant="1" id="Rfh-vR-JWI"/>
<constraint firstItem="iHQ-9O-6Vz" firstAttribute="top" secondItem="55g-fd-LTL" secondAttribute="bottom" constant="1" id="T2v-t9-1Ii"/>
<constraint firstItem="32b-om-4Ux" firstAttribute="top" secondItem="OEa-iz-t0R" secondAttribute="topMargin" constant="30" id="eLI-Ma-Pwa"/>
<constraint firstItem="bGx-Yf-Gjd" firstAttribute="top" secondItem="5Oa-0J-dF1" secondAttribute="bottom" constant="-12" id="g1H-4E-EL9"/>
<constraint firstAttribute="trailingMargin" secondItem="m8D-IM-8fn" secondAttribute="trailing" id="jzP-r4-iTn"/>
<constraint firstItem="1VL-rV-Hrb" firstAttribute="top" secondItem="bGx-Yf-Gjd" secondAttribute="top" id="oMm-uB-GeM"/>
<constraint firstItem="55g-fd-LTL" firstAttribute="leading" secondItem="OEa-iz-t0R" secondAttribute="leadingMargin" id="q1Z-Gp-ECh"/>
<constraint firstItem="bGx-Yf-Gjd" firstAttribute="centerX" secondItem="5Oa-0J-dF1" secondAttribute="trailing" multiplier="0.4" priority="250" id="qSG-OI-p1g"/>
<constraint firstItem="32b-om-4Ux" firstAttribute="centerX" secondItem="OEa-iz-t0R" secondAttribute="centerX" id="rKf-aR-afn"/>
<constraint firstAttribute="trailingMargin" secondItem="B0s-gC-ToH" secondAttribute="trailing" id="sbX-LJ-tAM"/>
<constraint firstItem="5Oa-0J-dF1" firstAttribute="trailing" secondItem="m8D-IM-8fn" secondAttribute="trailing" id="tTu-4R-rwV"/>
</constraints>
</tableViewCellContentView>
<connections>
<outlet property="dateLabel" destination="55g-fd-LTL" id="Tfw-j5-0F5"/>
<outlet property="endArrow" destination="1VL-rV-Hrb" id="Ism-o5-Qpa"/>
<outlet property="maxLabel" destination="B0s-gC-ToH" id="zzZ-p4-f1K"/>
<outlet property="minLabel" destination="r3Y-fY-1x1" id="7UF-gj-hhh"/>
<outlet property="startArrow" destination="bGx-Yf-Gjd" id="g2G-9T-fma"/>
<outlet property="startEndLabel" destination="32b-om-4Ux" id="Hks-Mk-LJS"/>
<outlet property="theSlider" destination="5Oa-0J-dF1" id="WLd-hq-5Yx"/>
</connections>
</tableViewCell>
</prototypes>
<connections>
<outlet property="dataSource" destination="J6Q-B3-lSE" id="Bup-OR-ep9"/>
<outlet property="delegate" destination="J6Q-B3-lSE" id="l97-XW-bAr"/>
</connections>
</tableView>
<navigationItem key="navigationItem" id="xho-8q-jym"/>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="YGx-B0-5lF" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-759.5419847328244" y="1776.7605633802818"/>
</scene>
</scenes>
<resources>
<image name="arrowshape.up" catalog="system" width="123" height="128"/>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
<systemColor name="systemRedColor">
<color red="1" green="0.23137254900000001" blue="0.18823529410000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
</resources>
</document>
和这段代码:
class SliderCell: UITableViewCell {
// startTime and endTime are in Percentages
public var startTime: Double = 0.0 { didSet { setNeedsLayout() } }
public var endTime: Double = 0.0 { didSet { setNeedsLayout() } }
@IBOutlet var startArrow: UIImageView!
@IBOutlet var endArrow: UIImageView!
@IBOutlet var dateLabel: UILabel!
@IBOutlet var startEndLabel: UILabel!
@IBOutlet var minLabel: UILabel!
@IBOutlet var maxLabel: UILabel!
@IBOutlet var theSlider: UISlider!
private var startConstraint: NSLayoutConstraint!
private var endConstraint: NSLayoutConstraint!
override func layoutSubviews() {
super.layoutSubviews()
// the thumb "circle" extends to the bounds / frame of the slider
// so, this is how we get the
// thumb center-to-center
// when value is 0 or 1.0
let trackRect = theSlider.trackRect(forBounds: theSlider.bounds)
let thumbRect = theSlider.thumbRect(forBounds: theSlider.bounds, trackRect: trackRect, value: 0.0)
let rangeWidth = theSlider.bounds.width - thumbRect.width
// Zero will be 1/2 of the width of the thumbRect
// minus 2 (because the thumb image is slightly offset from the thumb rect)
let xOffset = (thumbRect.width * 0.5) - 2.0
// create the arrow constraints if needed
if startConstraint == nil {
startConstraint = startArrow.centerXAnchor.constraint(equalTo: theSlider.leadingAnchor)
startConstraint.isActive = true
}
if endConstraint == nil {
endConstraint = endArrow.centerXAnchor.constraint(equalTo: theSlider.leadingAnchor)
endConstraint.isActive = true
}
// set arrow constraint constants
startConstraint.constant = rangeWidth * startTime + xOffset
endConstraint.constant = rangeWidth * endTime + xOffset
}
}
struct MyTimeInfo {
var startTime: Date = Date()
var endTime: Date = Date()
}
class SliderTableVC: UITableViewController {
var myData: [MyTimeInfo] = []
var minTime: Double = 0
var maxTime: Double = 24
var minTimeStr: String = ""
var maxTimeStr: String = ""
var timeRange: Double = 24
override func viewDidLoad() {
super.viewDidLoad()
// let's generate some sample data
let starts: [Double] = [
8, 7, 11, 10.5, 8.25, 9,
]
let ends: [Double] = [
20, 23, 19, 16.5, 21.75, 21,
]
let y = 2023
let m = 11
var d = 1
for (s, e) in zip(starts, ends) {
var dateComponents = DateComponents()
dateComponents.year = y
dateComponents.month = m
dateComponents.day = d
dateComponents.hour = Int(s)
dateComponents.minute = Int((s - Double(Int(s))) * 60.0)
let sDate = Calendar.current.date(from: dateComponents)!
dateComponents.hour = Int(e)
dateComponents.minute = Int((e - Double(Int(e))) * 60.0)
let eDate = Calendar.current.date(from: dateComponents)!
myData.append(MyTimeInfo(startTime: sDate, endTime: eDate))
d += 1
}
minTime = starts.min() ?? 0
maxTime = ends.max() ?? 24
timeRange = maxTime - minTime
minTimeStr = timeStringFromDouble(minTime)
maxTimeStr = timeStringFromDouble(maxTime)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myData.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let c = tableView.dequeueReusableCell(withIdentifier: "sliderCell", for: indexPath) as! SliderCell
let calendar = Calendar.current
var h = calendar.component(.hour, from: myData[indexPath.row].startTime)
var m = calendar.component(.minute, from: myData[indexPath.row].startTime)
let s: Double = Double(h) + Double(m) / 60.0
h = calendar.component(.hour, from: myData[indexPath.row].endTime)
m = calendar.component(.minute, from: myData[indexPath.row].endTime)
let e: Double = Double(h) + Double(m) / 60.0
let sPct: Double = (s - minTime) / timeRange
let ePct: Double = (e - minTime) / timeRange
let df = DateFormatter()
df.timeStyle = .short
let sStr = df.string(from: myData[indexPath.row].startTime)
let eStr = df.string(from: myData[indexPath.row].endTime)
df.dateStyle = .short
df.timeStyle = .none
c.dateLabel.text = df.string(from: myData[indexPath.row].startTime)
c.startTime = max(sPct, 0.0)
c.endTime = min(ePct, 1.0)
c.startEndLabel.text = sStr + " - " + eStr
c.minLabel.text = minTimeStr
c.maxLabel.text = maxTimeStr
return c
}
func timeStringFromDouble(_ t: Double) -> String {
let df = DateFormatter()
df.timeStyle = .short
var dateComponents = DateComponents()
dateComponents.hour = Int(t)
dateComponents.minute = Int((t - Double(Int(t))) * 60.0)
var date = Calendar.current.date(from: dateComponents)!
return df.string(from: date)
}
}