问题
I am struggling to properly use UISliders in a UITableViewCell. Here is the Idea:
- The User can set different Jobs which he needs to fulfil tasks. The amount of different Jobs cannot be known in advance. Lets say for example User1 needs for his Band only Guitarists and Drummers, where someone else needs Guitarists, Drummers, Singers, Keyboarders and so on.
What I want him to provide is that depending on which Jobs he needs, he can set the amount of people he needs for a specific job. I am using a UISlider in a TableViewCell to let him do this.
But here comes the trouble:
(1) The Label under the UISlider doesn't show the right amount while the User drags the Slider
(2) The prepareForReuse function doesn't work properly. I want the User to have the Slider back at 0 and the Label showing 0 when a reused Cell gets used.
(3) Finally the jobAmountNeeded
Array doesn't have the right values stored (or no values at all) and therefore the saved Amounts in Firebase aren't right.
In my opinion all three Problems are connect to the same source.
Therefore I would be more than pleased, if someone could help me. :)
Thanks for your help everyone!
class ProjectTeamViewController: UIViewController {
// MARK: - Properties
@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var headerSeperator: UIView!
@IBOutlet weak var saveButton: UIButton!
var sortedSoloJobs = [String]()
var jobIndex: Int?
var jobAmount: [String] = []
var selectedJob:String?
var sentProjectCreated:String?
var projectCreated:String?
var jobAmountNeeded = [JobNeeded]()
var slider: [SliderClass] = []
let step:Float = 1
var selectedIndexPath: IndexPath?
var tempArray = [99: "Baum"]
var tempArray2 = [99: 5]
var currentValue:Float = 0
var key = 0
override func viewDidLoad() {
super.viewDidLoad()
setUpUI ()
getJobs(for: User.current) { (memberJobs) in
self.tableView.reloadData()
}
}
// MARK: - Functions
func setUpUI () {
Utilities.addShadowtoView(headerSeperator)
Utilities.addShadowtoButton(saveButton)
saveButton.layer.cornerRadius = 6
saveButton.layer.borderWidth = 1
saveButton.layer.borderColor = UIColor.darkGray.cgColor
}
func getJobs(for user: User, completion: @escaping ([MemberJobsStruct]) -> Void) {
var jobs = [String]() // All Jobs
var soloJobs = [String]() // Solo jobs from all jobs
let ref = Database.database().reference().child("team").child(user.uid)
ref.observeSingleEvent(of: .value, with: { snapshot in
for case let child as DataSnapshot in snapshot.children {
guard let value = child.value as? [String: Any] else {
return completion ([]) }
let memberJob = value["memberJob"] as! String
jobs.append(memberJob)
soloJobs = Array(Set(jobs))
self.sortedSoloJobs = soloJobs.sorted()
DispatchQueue.main.async { self.tableView.reloadData() }
} })
}
func json(from object:Any) -> String? {
guard let data = try? JSONSerialization.data(withJSONObject: object, options: []) else {
return nil
}
return String(data: data, encoding: String.Encoding.utf8)
}
@objc func sliderValueChange(sender: UISlider){
sender.isContinuous = false
currentValue = round(sender.value / step) * step
var sliderValue = Int(currentValue)
sender.value = currentValue // get slider's value
let row = sender.tag // get slider's row in table
key = row
if let row = self.jobAmountNeeded.firstIndex(where: {$0.jobKey == key}) {
jobAmountNeeded[row].jobAmount = sliderValue
}
}
@IBAction func saveAction(_ sender: Any) {
if projectCreated == nil || projectCreated == "no Value" {
let alert = UIAlertController(title: "Error", message: "Internal Error send a bug report: #150420", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
alert.addAction(UIAlertAction(title: "Cancel", style: .default, handler: nil))
self.present(alert, animated: true)
} else {
for value in jobAmountNeeded {
let jobName = value.jobName
let jobAmount = value.jobAmount
let jobKey = value.jobKey
let currentUser = User.current
let jobreqRef = Database.database().reference().child("posts").child(currentUser.uid).child(projectCreated ?? "no Value").child("ProjectJobAmount").child(value.jobName)
let project = JobNeeded(jobName: jobName, jobAmount: jobAmount, jobKey: jobKey)
let projectDict = project.dictValue
jobreqRef.setValue(projectDict) { err, jobreqRef in
// print("done")
}
}
}
}
@IBAction func cancelAction(_ sender: Any) {
self.view.window?.rootViewController?.dismiss(animated: true, completion: nil)
}
}
extension ProjectTeamViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sortedSoloJobs.count }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let sortedSoloJob = sortedSoloJobs[indexPath.row]
let key = indexPath.row
let cell = tableView.dequeueReusableCell(withIdentifier: "ProjectCharacterTableViewCell", for: indexPath) as! ProjectCharacterTableViewCell
cell.jobNameLabel.text = sortedSoloJobs[indexPath.row]
jobAmountNeeded.append(JobNeeded(jobName: sortedSoloJob, jobAmount: 0, jobKey: indexPath.section))
if let row = self.jobAmountNeeded.firstIndex(where: {$0.jobName == sortedSoloJob}) {
jobAmountNeeded[row].jobName = sortedSoloJob
}
tempArray.updateValue(sortedSoloJob, forKey: key)
let currentValue = cell.slider.value
cell.slider.isContinuous = false
cell.slider.tag = key
cell.slider.minimumValue = 0
cell.slider.maximumValue = Float(sortedSoloJob.count)
cell.slider.addTarget(self, action: #selector(sliderValueChange), for: .valueChanged)
cell.jobAmountLabel.text = "\(currentValue)"
return cell
}
}
// MARK: - UITableViewDelegate
extension ProjectTeamViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 163
}
}
class SliderClass: NSObject {
var title: String?
var subtitle: String?
var sliderMinimum: Float?
var sliderMaximum: Float?
init(title: String, subtitle: String, sliderMinimum: Float, sliderMaximum: Float) {
self.title = title
self.subtitle = subtitle
self.sliderMinimum = sliderMinimum
self.sliderMaximum = sliderMaximum
}
}
class ProjectCharacterTableViewCell: UITableViewCell {
@IBOutlet weak var jobAmountLabel: UILabel!
@IBOutlet weak var slider: UISlider!
@IBOutlet weak var jobNameLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
}
override public func prepareForReuse() {
// Ensures the reused cosmos view is as good as new
super.prepareForReuse()
self.jobAmountLabel.text = "0"
self.slider.value = 0
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
回答1:
Start simpler...
First, let's add a "current value" property to your SliderClass
(I'm calling it a SoloJob
class, as it seems more logical):
class SoloJob: NSObject {
var title: String = ""
var subtitle: String = ""
var sliderMinimum: Float = 0
var sliderMaximum: Float = 100
var currentValue: Float = 0
init(title: String, subtitle: String, sliderMinimum: Float, sliderMaximum: Float, currentValue: Float) {
self.title = title
self.subtitle = subtitle
self.sliderMinimum = sliderMinimum
self.sliderMaximum = sliderMaximum
self.currentValue = currentValue
}
}
We'll use the currentValue
property to keep track of the slider value.
So, create a cell with a "title" label, a slider, and a "job amount" (or current value) label. I have it laid out like this:
In your cell class, connect the slider to an @IBAction
for when it changes - NOT in your controller class.
Also in your cell class, add a "callback" closure var:
// closure to tell controller the slider was changed
var callback: ((Float) -> ())?
then, in your @IBAction
func:
@IBAction func sliderValueChange(_ sender: UISlider) -> Void {
let v = sender.value
// update the label
jobAmountLabel.text = "Current Amount: \(Int(v))"
// tell the controller the slider changed
callback?(v)
}
Back in your controller class, in cellForRowAt
, setup the "callback" closure:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ProjectCharacterTableViewCell", for: indexPath) as! ProjectCharacterTableViewCell
let thisJob: SoloJob = sortedSoloJobs[indexPath.row]
// set values / labels in the cell
// closure to get notified when the slider is changed
cell.callback = { val in
// update our data
self.sortedSoloJobs[indexPath.row].currentValue = val
}
return cell
}
When the user drags the slider, @IBAction func sliderValueChange()
in the cell class itself will be called, and that's where we update the label in the cell and tell the controller the value changed.
Here is a complete implementation:
import UIKit
class SoloJob: NSObject {
var title: String = ""
var subtitle: String = ""
var sliderMinimum: Float = 0
var sliderMaximum: Float = 100
var currentValue: Float = 0
init(title: String, subtitle: String, sliderMinimum: Float, sliderMaximum: Float, currentValue: Float) {
self.title = title
self.subtitle = subtitle
self.sliderMinimum = sliderMinimum
self.sliderMaximum = sliderMaximum
self.currentValue = currentValue
}
}
class ProjectCharacterTableViewCell: UITableViewCell {
@IBOutlet weak var jobAmountLabel: UILabel!
@IBOutlet weak var slider: UISlider!
@IBOutlet weak var jobNameLabel: UILabel!
// closure to tell controller the slider was changed
var callback: ((Float) -> ())?
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
contentView.layer.cornerRadius = 12
contentView.layer.borderColor = UIColor.blue.cgColor
contentView.layer.borderWidth = 1
contentView.layer.masksToBounds = true
contentView.backgroundColor = .white
backgroundColor = .clear
}
func configureCell(_ theJob: SoloJob) -> Void {
jobNameLabel.text = theJob.title + " - min: \(Int(theJob.sliderMinimum)) / max: \(Int(theJob.sliderMaximum))"
slider.minimumValue = theJob.sliderMinimum
slider.maximumValue = theJob.sliderMaximum
slider.value = theJob.currentValue
jobAmountLabel.text = "Current Amount: \(Int(theJob.currentValue))"
}
// connect valueChanged action in Storyboard
@IBAction func sliderValueChange(_ sender: UISlider) -> Void {
let v = sender.value
// update the label
jobAmountLabel.text = "Current Amount: \(Int(v))"
// tell the controller the slider changed
callback?(v)
}
}
class ProjectTeamViewController: UIViewController {
@IBOutlet weak var tableView: UITableView!
var sortedSoloJobs: [SoloJob] = []
override func viewDidLoad() {
super.viewDidLoad()
// create some example data
for i in 1...20 {
// random slider minimum between 0 and 2
let minVal = Int.random(in: 0...2)
// random slider maximum between 5 and 10
let maxVal = Int.random(in: 5...10)
// start with current value at minimum
let curVal = minVal
let job = SoloJob(title: "Job Name \(i)", subtitle: "", sliderMinimum: Float(minVal), sliderMaximum: Float(maxVal), currentValue: Float(curVal))
sortedSoloJobs.append(job)
}
tableView.dataSource = self
tableView.delegate = self
}
}
extension ProjectTeamViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sortedSoloJobs.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ProjectCharacterTableViewCell", for: indexPath) as! ProjectCharacterTableViewCell
let thisJob: SoloJob = sortedSoloJobs[indexPath.row]
cell.configureCell(thisJob)
// closure to get notified when the slider is changed
cell.callback = { val in
// update our data
self.sortedSoloJobs[indexPath.row].currentValue = val
}
return cell
}
}
extension ProjectTeamViewController: UITableViewDelegate {
}
and the Storyboard source (with all the @IBOutlet
and @IBAction
connections):
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16096" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="OoM-UM-qa5">
<device id="retina4_0" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Project Team View Controller-->
<scene sceneID="LA9-sV-8lR">
<objects>
<viewController id="OoM-UM-qa5" customClass="ProjectTeamViewController" customModule="TableAdd" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="GWK-to-6GG">
<rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="I5Z-lW-4b3">
<rect key="frame" x="20" y="20" width="280" height="30"/>
<color key="backgroundColor" red="0.92143100499999997" green="0.92145264149999995" blue="0.92144101860000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<state key="normal" title="SAVE BUTTON"/>
</button>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Rkw-MO-6Op" userLabel="Horizontal Line View">
<rect key="frame" x="20" y="58" width="280" height="1"/>
<color key="backgroundColor" red="0.92143100499999997" green="0.92145264149999995" blue="0.92144101860000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="1" id="BkU-lx-Zp8"/>
</constraints>
</view>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="pgu-lS-tk6">
<rect key="frame" x="20" y="67" width="280" height="481"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="ProjectCharacterTableViewCell" rowHeight="109" id="tnK-1p-f4N" customClass="ProjectCharacterTableViewCell" customModule="TableAdd" customModuleProvider="target">
<rect key="frame" x="0.0" y="28" width="280" height="109"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="tnK-1p-f4N" id="gcG-sV-dlw">
<rect key="frame" x="0.0" y="0.0" width="280" height="109"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Job Name Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="l3R-9V-mjm">
<rect key="frame" x="78" y="11" width="124" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<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="mj1-CV-iWZ">
<rect key="frame" x="13" y="36" width="254" height="31"/>
<connections>
<action selector="sliderValueChange:" destination="tnK-1p-f4N" eventType="valueChanged" id="RkI-oL-0eQ"/>
</connections>
</slider>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Job Amount Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="xMH-9r-GO9">
<rect key="frame" x="70.5" y="70" width="139" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<constraints>
<constraint firstItem="l3R-9V-mjm" firstAttribute="top" secondItem="gcG-sV-dlw" secondAttribute="topMargin" id="DPl-Kl-d1J"/>
<constraint firstItem="mj1-CV-iWZ" firstAttribute="leading" secondItem="gcG-sV-dlw" secondAttribute="leadingMargin" id="Sx7-a7-Yxy"/>
<constraint firstItem="mj1-CV-iWZ" firstAttribute="top" secondItem="l3R-9V-mjm" secondAttribute="bottom" constant="4" id="Z05-fI-eal"/>
<constraint firstItem="xMH-9r-GO9" firstAttribute="top" secondItem="mj1-CV-iWZ" secondAttribute="bottom" constant="4" id="a8n-XL-xxa"/>
<constraint firstAttribute="bottomMargin" relation="greaterThanOrEqual" secondItem="xMH-9r-GO9" secondAttribute="bottom" id="cg5-O7-mnS"/>
<constraint firstItem="l3R-9V-mjm" firstAttribute="centerX" secondItem="gcG-sV-dlw" secondAttribute="centerX" id="hGU-ad-se2"/>
<constraint firstItem="xMH-9r-GO9" firstAttribute="centerX" secondItem="gcG-sV-dlw" secondAttribute="centerX" id="p4W-nU-hxy"/>
<constraint firstAttribute="trailingMargin" secondItem="mj1-CV-iWZ" secondAttribute="trailing" id="umL-5D-BUa"/>
</constraints>
</tableViewCellContentView>
<connections>
<outlet property="jobAmountLabel" destination="xMH-9r-GO9" id="AIQ-ro-Q2C"/>
<outlet property="jobNameLabel" destination="l3R-9V-mjm" id="cA7-Kq-aRd"/>
<outlet property="slider" destination="mj1-CV-iWZ" id="YDo-wV-0rA"/>
</connections>
</tableViewCell>
</prototypes>
</tableView>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<constraints>
<constraint firstItem="I5Z-lW-4b3" firstAttribute="leading" secondItem="iax-Rw-gHC" secondAttribute="leading" constant="20" id="8bZ-vh-e2L"/>
<constraint firstItem="pgu-lS-tk6" firstAttribute="leading" secondItem="iax-Rw-gHC" secondAttribute="leading" constant="20" id="KK9-MN-7TR"/>
<constraint firstItem="I5Z-lW-4b3" firstAttribute="top" secondItem="iax-Rw-gHC" secondAttribute="top" constant="20" id="MBM-in-OG7"/>
<constraint firstItem="Rkw-MO-6Op" firstAttribute="leading" secondItem="iax-Rw-gHC" secondAttribute="leading" constant="20" id="NM3-Ah-IIR"/>
<constraint firstItem="pgu-lS-tk6" firstAttribute="top" secondItem="Rkw-MO-6Op" secondAttribute="bottom" constant="8" id="abw-Kr-4qh"/>
<constraint firstItem="iax-Rw-gHC" firstAttribute="bottom" secondItem="pgu-lS-tk6" secondAttribute="bottom" constant="20" id="dBK-83-lBg"/>
<constraint firstItem="iax-Rw-gHC" firstAttribute="trailing" secondItem="I5Z-lW-4b3" secondAttribute="trailing" constant="20" id="erM-u3-zLO"/>
<constraint firstItem="Rkw-MO-6Op" firstAttribute="top" secondItem="I5Z-lW-4b3" secondAttribute="bottom" constant="8" id="ry1-D5-U89"/>
<constraint firstItem="iax-Rw-gHC" firstAttribute="trailing" secondItem="Rkw-MO-6Op" secondAttribute="trailing" constant="20" id="vhe-jw-Dnb"/>
<constraint firstItem="iax-Rw-gHC" firstAttribute="trailing" secondItem="pgu-lS-tk6" secondAttribute="trailing" constant="20" id="zdu-4p-FAt"/>
</constraints>
<viewLayoutGuide key="safeArea" id="iax-Rw-gHC"/>
</view>
<connections>
<outlet property="tableView" destination="pgu-lS-tk6" id="08E-xc-PqA"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="Ns0-iW-ioz" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-534.375" y="839.78873239436621"/>
</scene>
</scenes>
</document>
Result:
And because we're updating our data array whenever a slider is changed, we can scroll through the table and reused cells will be configured properly.
When all of that makes sense, carry the methods over to your project to match your layout and data structuring.
来源:https://stackoverflow.com/questions/63850330/swift-using-a-uislider-in-a-uitableviewcell-the-right-way