2017-09-13 114 views
0
我在与UITableViewCells不加载,除非我向下滚动,揭示他们的问题

,问题是每个自定义单元格在他们一个拨动开关,如果它是在后来我补充一点,细胞的值进行计算,但是由于缺少一些未加载的单元格(一旦向下滚动,它们会加载),计算就不完整。
希望这是有道理的。这是在一个蓝牙连接应用程序,它应该刷新表,每次它收到新的值。这里的加载细胞代码:加载UITableViewCells不在显示

//TABLE LOADING/RELOADING FUNCTIONS: 
func numberOfSections(in tableView: UITableView) -> Int { 
    return 1 

} 

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{ 
     return temperatureReadingArray.count //Should return 1 or 9 depending on array received 
} 

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 

    let cell = tableView.dequeueReusableCell(withIdentifier: temperatureReading) as! TemperatureReading 

    cell.channelLabel.text = channelNames[indexPath.row] 
    cell.readingLabel.text = temperatureReadingArray[indexPath.row] 

    if (channelNames.count == 1) 
    { 
    cell.toggleSwitch.isHidden = true 
    } 
    if (cell.toggleSwitch.isOn) 
    { 
     if let value = Double(cell.readingLabel.text!) 
     { 
      print("valid number: \(value), appending") 
      TNUArray.append(value) 
     } 
     else 
     { 
      print("Not a valid number reading") 
     } 
    } 
    return cell 
} 

正如你所看到的,我检查拨动开关,到目前为止,该代码的工作,因为我打算,假设所有细胞适应可见的窗口内,但是当我运行有一个小屏幕的设备上,或者如果我得到太多的值(超过适合屏幕),那么不可见没有得到朝TNU计算细胞计数:

func updateIncomingData() { 

    print ("Received String:") 
    print (receivedDataString) 
    print("Clearing TNU Array") 
    TNUArray.removeAll() 
    temperatureReadingArray = receivedDataString.components(separatedBy: " ") 
    self.temperatureReadingsTable.reloadData() 
    DispatchQueue.main.async { 
     self.calculateTNU() //Need dispatch queue to run this AFTER it finished reloading data 
    } 
} 

func calculateTNU() 
{ 
    var TNU: Double = 0.000 

    let minValue = TNUArray.min() 
    let maxValue = TNUArray.max() 
    print(TNUArray) 
    if (minValue != nil && maxValue != nil) 
    { 
     TNU = maxValue! - minValue! 
     calculatedTNU = String(format:"%.3f", TNU) 
     TNULabel.text = calculatedTNU 
    } 
    else 
    { 
     print("Max or Min had nil values") 
     TNULabel.text = "NA" 
    } 
} 

我希望我是清楚了,我会试着总结一下它的,都应该发生的事情的顺序:
- 接收蓝牙字符串(它CONTA INS)用空格分隔的多个值
- 解析这些值转换为数值数组,让我们假设我得到9个价值
- 添加一个值,每个细胞,具有一个切换开关
- 检查该单元的开关已打开并且如果它已经该值然后添加到一个数组,以计算
值之间的差MAX - 计算最大值 - 最小值的值
- 显示值。

的问题是计算因为它使用来计算它需要ALL细胞加载阵列的最大值 - 最小值的值,并且不适合在屏幕的细胞不加载除非我滚动。
注:我也尝试过使用DispatchQueue.main.async和reloadData(),但是这并不能解决问题,因为它不会加载不在显示

+0

在MVC范例下,您需要将单元格中的切换数据存储在您的数据模型中,而不是依赖于能够从单元格本身读取 - 当单元格从视图中消失时,它会自动出列,信息。一个简单的方法是维护和数组或字典,告诉你哪些索引路径包含切换的单元格 - 唯一的问题是如果你的表格视图是可编辑的,所以你需要小心。希望有所帮助。 – Sparky

+0

我喜欢这个想法,我正在探索这个概念。只是一个简单的问题,说我切换单元格上的开关,我如何告诉数组/字典“嗨,它是单元格X切换,而不是单元格Y”,我该如何引用单元格内的单元格索引? –

+0

你可以做的一件事是子类UICollectionViewCell,建立一个委托协议并使你的集合视图符合该协议。然后,您可以让单元格通知委托单元何时切换,然后集合视图可以更新模型(如果您不知道内置于UiCollectionView API中的方法可以检索单元格的索引路径) 。如果你对此不清楚,可能会在聊天中给我留言(而不是扩展此主题)。 – Sparky

回答

0

谢谢你的建议。截至目前,我采用了更基本的方法,因为我还不是很精通:
我做了一个额外的数组,它保存了每个单元格的索引,这个数组对于所有其他视图控制器都是全局访问的。基本上我在初始化时,其中添加了此阵对细胞数: 因为我将永远不会收到超过9个频道我做了这个:

var toggleSwitchStates:[Bool] = [true, true, true, true, true, true, true, true, true] 

这是我的电池现在的代码:

class TemperatureReading: UITableViewCell { 

    var cellIndex: Int? 
    @IBOutlet weak var channelLabel: UILabel! 

    @IBOutlet weak var readingLabel: UILabel! 

    @IBOutlet weak var toggleSwitch: UISwitch! 

    @IBAction func switchWasToggled(_ sender: UISwitch) { 
     print ("Switch toggled for: " + channelLabel.text!) 
     changeArrayValue() 
    } 

    func changeArrayValue() 
    { 
     toggleSwitchStates[cellIndex!] = toggleSwitch.isOn 
     NotificationCenter.default.post(name: Notification.Name(rawValue: TOGGLESWITCH_TOGGLED), object: nil) //Let the ViewController know there was a change, re-load TNU data 
    } 

这是我对它们进行初始化:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 

     //THIS CODE INITIALIZES EACH CELL, WITH ITS CORRESPONDING VALUES 
     let cell = tableView.dequeueReusableCell(withIdentifier: temperatureReading) as! TemperatureReading 

     cell.channelLabel.text = channelNames[indexPath.row] 
     cell.readingLabel.text = temperatureReadingArray[indexPath.row] 

     if (channelNames.count == 1) 
     { 
     cell.toggleSwitch.isHidden = true //IF there's only one channel, no need to calculate TNU 
     } 
     cell.toggleSwitch.isOn = toggleSwitchStates[indexPath.row] 
     cell.cellIndex = chIndex[indexPath.row] 

     return cell 
    } 

,最后通过两个阵列中运行计算:

func calculateTNU() 
    { 
     TNUArray.removeAll() 
     var TNU: Double = 0.000 
     for (index, _) in temperatureReadingArray.enumerated() //First we populate the array based on the toggle switches 
     { 
      if (toggleSwitchStates[index] == true) 
      { 
       if let value = Double(temperatureReadingArray[index]) 
       { 
        TNUArray.append(value) 
        print("Appending value to array: \(value)") 
       } 
       else 
       { 
        print("not a valid number") 
       } 
      } 
      else 
      { 
       print("toggle switch is off") 
      } 
     } //Now we get max - min values 
     let minValue = TNUArray.min() 
     let maxValue = TNUArray.max() 
     print("TNU Array is:\(TNUArray)") 
     if (minValue != nil && maxValue != nil) 
     { 
      TNU = maxValue! - minValue! 
      calculatedTNU = String(format:"%.3f", TNU) //3 decimal places 
      TNULabel.text = calculatedTNU 
     } 
     else 
     { 
      print("Max or Min had nil values") 
      TNULabel.text = "NA" 
     } 
    } 
0

细胞正如你已经注意到了,这里的问题是cellForRowAtIndexPath方法仅针对可见单元格调用,这就是为什么计算时不考虑屏幕外的单元格的原因。

我会建议你的是创建一个类,你将存储所有蓝牙相关的对象,只有做计算时,你被你的方法完成处理通知。

要做到这一点,你可以创建一个单从任何地方访问你的项目中的网络类,并确保您始终只有它的一个实例在运行:

class Networking { 

    static let shared = Networking() 
} 

现在创建一个数组来保存你的数据,并把您的updateIncomingData()函数在那里,并添加完成处理它,就像这样:

var temperatureReadingArray = [Int]() 

func updateIncomingData(_ completion: @escaping() ->()) { 
    // ..Do your networking/bluetooth operations here.  
    self?.temperatureReadingArray.append(newTemperature)  
    completion() 
} 

这可以让你得到通知,每当一些新的数据(newTemperature)来了,让你可以知道何时需要重新加载数据(无需垫子)之三的细胞是可见或不可见)

现在回到你的表视图控制器,并在您的viewDidLoad方法(或任何你想要的,无论是按钮等。),执行以下操作,通过完成处理通知,并重新加载异步您的表视图:

Networking.shared.loadMoreData { [weak self] in 
    self?.tableView.reloadData() 
    self?.doSomeCalculations() 
} 

而且不要忘记更新您的数据源,以便它反映了你的阵列内部的温度对象的数量:

// MARK: UITableViewDataSource 

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 
    return Networking.shared.temperatureReadingArray.count 
} 

最后这里是链接到一个项目,我做给你看它是如何工作:

https://www.dropbox.com/s/lta464kl3ysilct/DisplayMinMax.zip?dl=0

如果您有任何问题,请告诉我!

更新:

这里是你如何能够使用开关和封锁来获得细胞的开关状态做到这一点:

首先,你需要一个定制的表格视图单元格。内部的自定义类创建两个瓶盖,一个用于对开关,一个为开关关断:

class CustomTableCell: UITableViewCell { 

    var shouldToggleOn: ((CustomTableCell) -> Void)? 
    var shouldToggleOff: ((CustomTableCell) -> Void)? 

    // MARK: User Interaction 

    @IBAction func handleSwitchValueChanged(_ sender: UISwitch) { 

     if sender.isOn { 

      shouldToggleOn?(self) 

     } else { 

      shouldToggleOff?(self) 
     } 

    } 
} 

现在让我们假设你有一个Temperature对象,看起来像这样:

class Temperature : NSObject { 

    // MARK: Properties 

    var value: Int 
    var boolean: Bool 

    // MARK: Initializers 

    init(value: Int, boolean: Bool) { 
     self.value = value 
     self.boolean = boolean 
    } 
} 

在你网络类创建一个数组来保存你所有的温度:

var dataArray = [Temperature]() 

每当你的loadMoreData被调用,您可以将新的温度添加到您的阵列,像这样:

dataArray.append(Temperature(value: randomNumber, boolean: false)) 

注:这真是你来选择是否要布尔值默认为真或假。

现在在您的方法cellForRowAtIndexPath中,是时候使用您之前创建的两个闭包,并且如果用户将开关打开或关闭,则分别更新数据模型。

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 

      // ... 

      cell.shouldToggleOn = { (selectedCell) -> Void in 
       print("turned ON") 
       Networking.shared.dataArray[indexPath.row].boolean = true 
      } 

      cell.shouldToggleOff = { (selectedCell) -> Void in 
       print("turned OFF...") 
       Networking.shared.dataArray[indexPath.row].boolean = false 
      } 

      // ... 
} 

现在,每当你收到一个新的温度,你loadMoreData方法完成处理程序(我建议你不要把你的cellForRow方法里面,而是在你的viewDidLoad方法)将被调用。

那时候,你可以重新载入你的表视图,并开始计算:

Networking.shared.loadMoreData { [weak self] in 
    self?.tableView.reloadData() 
    self?.doSomeCalculations() 
} 

并为您的doSomeCalculations方法,只是过滤和映射到选择你是否需要做计算(又名是否布尔是ON或OFF):

func doSomeCalculations() { 

     // consider only toggled on switches 

     let onlyToggledOnArray = Networking.shared.dataArray.filter { $0.boolean == true } 

     // get the min and max values 

     let maxValue = onlyToggledOnArray.map{$0.value}.max() 
     let minValue = onlyToggledOnArray.map{$0.value}.min() 

     guard maxValue != nil, minValue != nil else { 
     return 
     } 

     // ... 
} 

,一切都应该只是正常工作:)

+0

谢谢你,我已经按照你的建议设置了它。我确实有一个BLE类和快速文件与其他文件分开。问题不在于此。问题是每个单元格都有一个切换开关,我只对具有切换开关的单元格执行计算。这是问题所在,因为没有将不可见的切换“ON”单元考虑在内。我不能只处理所有的数据,这就打破了拨动开关的目的 –

+0

哦,好吧,谢谢你的澄清!在这种情况下,我会建议您为对象模型添加一个布尔属性,以便引用它并检查它是否打开,以便您可以选择执行计算。并且要知道哪个单元格已被切换,我会在自定义单元格类中使用闭包 – Alex

0

只需使用一个数据模型类和第初始化在课堂上,将在UITableView中填充所有的值。

假设如果所有的细胞都拨动开关然后初始化与布尔值(不同属性的不同细胞)的模型类,并设置它们的默认值(是/否)

而且在cellForRowAtIndexPath方法

if(modelClass.toggleValue) 
    cell.toggleSwitch.setOn(true) 
else 
    cell.toggleSwitch.setOn(false) 

而且,无论何时切换开关,都需要更新模型类以获得相应的值。你完成了。现在对存储在模型类中的值进行计算。不是直接从细胞中提取。

希望这会帮助你..!