2016-11-07 200 views
0

我正在試驗一個UITableView。基本上,一個單元格在選中時會展開,而其他所有單元格都應該處於原始高度。但不知何故,他們沒有。UITableview細胞高度bug

Expanded work fine when selecting the non-last cell

Expanded break when selecting the last cell

代碼:

@IBOutlet weak var searchBar: UISearchBar! 
// MARK: Instance Variables 
let database = DatabaseHandler.instance 
var allLoops = [SongLoop]() 
var sliderTimer: Timer! 
var loopPlayer: LoopPlayer! 

var overlayView: Overlay! 
var overlayViewLayer: CAShapeLayer! 
var openedIndexPath: IndexPath! 

// MARK: IBOutlet properties 
@IBOutlet weak var emptyView: UIView! 
@IBOutlet weak var tblExpandable: UITableView! 
@IBOutlet weak var addNewLoop: UIButton! 

// MARK: ViewController lifecycles 
override func viewDidLoad() { 
    super.viewDidLoad() 
    emptyView.isHidden = true 
    addNewLoop.isHidden = true 

    setUpOverlayView() 

    let height = UIApplication.shared.statusBarFrame.size.height 
    tblExpandable.contentInset = UIEdgeInsets(top: height, left: 0, bottom: 0, right: 0) 

    // Change color status bar 
    self.setNeedsStatusBarAppearanceUpdate() 
    self.tblExpandable.alwaysBounceVertical = false 
    self.searchBar.delegate = self 
} 

override func viewWillAppear(_ animated: Bool) { 
    super.viewWillAppear(animated) 
    let loadDatabaseThread = DispatchQueue.global(qos: DispatchQoS.QoSClass.userInitiated) 
    loadDatabaseThread.async { 
     self.allLoops = self.database.getLoops() 
     DispatchQueue.main.async { 
      self.tblExpandable.reloadData() 
      self.updateLoopTimeOnSlider() 
      if self.openedIndexPath != nil { 
       self.tblExpandable.reloadRows(at: [self.openedIndexPath!], with: .fade) 
       self.loopPlayer = LoopPlayer(loop: self.allLoops[self.openedIndexPath.row]) 
      } 
     } 
    } 
} 

override func viewWillDisappear(_ animated: Bool) { 
    super.viewWillDisappear(animated) 
    allLoops.removeAll() 
    stopUpdatingThumbPositionOnLoopSlider() 
    stopPlayingLoop() 
} 

// MARK: UITableView Delegate and Datasource Functions 
func scrollViewDidScroll(_ scrollView: UIScrollView) { 
    if openedIndexPath != nil { 
     var rect = overlayView.bounds 
     rect.origin.y = scrollView.contentOffset.y; 
     overlayView.frame = rect 

     let cell = self.tblExpandable.cellForRow(at: openedIndexPath) 

     if (cell != nil) { 
      let cellRect = overlayView.convert(cell!.frame, from: self.tblExpandable) 
      overlayView.selectedCellRect = cellRect 

      let topRect = CGRect(x: 0, y: 0, width: cellRect.size.width, height: cellRect.origin.y) 
      let bottomRect = CGRect(x: 0, y: cellRect.maxY - 1, width: cellRect.size.width, height: overlayView.frame.size.height - cellRect.maxY) 

      let path = UIBezierPath(rect: topRect) 
      let bottomPath = UIBezierPath(rect: bottomRect) 
      path.append(bottomPath) 

      overlayViewLayer.path = path.cgPath 
      overlayView.layer.mask = overlayViewLayer 
     } 
    } 
} 

func tableView(
    _ tableView: UITableView, 
    numberOfRowsInSection section: Int 
) -> Int { 
    let count = allLoops.count 
    if count == 0 { 
     emptyView.isHidden = false 
     addNewLoop.isHidden = false 
    } else { 
     addNewLoop.isHidden = true 
     emptyView.isHidden = true 
    } 
    return count 
} 

func tableView(
    _ tableView: UITableView, 
    cellForRowAt indexPath: IndexPath 
) -> UITableViewCell { 
    let cell = tableView.dequeueReusableCell(
     withIdentifier: "cellSong", 
     for: indexPath) as! LoopMainViewCell 

    let bgColorView = UIView() 
    bgColorView.backgroundColor = Colors.selectedCellBackground 
    cell.selectedBackgroundView = bgColorView 

    let currentLoop = self.allLoops[indexPath.row] 
    cell.songLabel.text = currentLoop.name 
    cell.artistLabel.text = currentLoop.song.artist 
    cell.durationLabel.text = formatDuration(Int(currentLoop.duration)) 
    cell.dateLabel.text = dateFormatter.string(from: currentLoop.updatedDate as Date) 
    cell.slider.value = 0.0 
    cell.slider.minimumValue = 0.0 
    cell.slider.maximumValue = Float(currentLoop.end - currentLoop.start) 
    cell.selectionStyle = .none 
    cell.playButton.setImage(Images.playButton, for: UIControlState()) 
    return cell 
} 

func tableView(
    _ tableView: UITableView, 
    heightForRowAt indexPath: IndexPath 
) -> CGFloat { 
    return openedIndexPath != nil 
     && openedIndexPath == indexPath 
     && openedIndexPath.row < allLoops.count ? 208 : 117 
} 

func tableView(
    _ tableView: UITableView, 
    didSelectRowAt indexPath: IndexPath 
) { 
    if searchBar.isFirstResponder { 
     searchBar.resignFirstResponder() 
     searchBar.endEditing(true) 
     searchBar.setShowsCancelButton(false, animated: true) 
    } 
    // Prevent reloading cell when it is expanded 
    if tableView.rectForRow(at: indexPath).size.height == 117 { 
     openedIndexPath = indexPath 
     self.loopPlayer = LoopPlayer(loop: allLoops[indexPath.row]) 

     tableView.reloadRows(at: [openedIndexPath], with: .fade) 
     overlayView.alpha = 0 
     tableView.addSubview(overlayView) 
     self.scrollViewDidScroll(tableView) 
     UIView.animate(withDuration: 0.3, animations: { 
      self.overlayView.alpha = 0.5 
     }) 
    } 

} 

// MARK: Event Handler Delegates 
override func remoteControlReceived(with event: UIEvent?) { 
    guard let event = event else { 
     print("No event received.") 
     return 
    } 
    guard event.type == UIEventType.remoteControl else { 
     print("Received non Remote Control event.") 
     return 
    } 
    switch event.subtype { 
    case UIEventSubtype.remoteControlPlay: 
     playLoop(playButton) 
    case UIEventSubtype.remoteControlPause: 
     pauseLoop(playButton) 
    case UIEventSubtype.remoteControlTogglePlayPause: 
     playLoop(playButton) 
    default: break 
    } 
} 

// MARK Delete action 
@IBAction func deleteLoop(_ sender: AnyObject) { 
    let item = loopPlayer.currentLoop 
    let selectedIndexPath = openedIndexPath 
    let title = "Delete \(item.name)?" 
    let ac = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) 

    // Delete action 
    let deleteAction = UIAlertAction(title: title, style: .destructive) { 
     (action) -> Void in 
      // Need method to delete loop 
      self.stopPlayingLoop() 
      self.database.deleteLoops([self.loopPlayer.currentLoop]) 
      self.allLoops.remove(at: self.openedIndexPath.row) 
      self.tblExpandable.deleteRows(at: [self.openedIndexPath], with: .automatic) 

      self.openedIndexPath = nil 
      if (selectedIndexPath?.row)! < self.allLoops.count { 
       self.tblExpandable.reloadRows(at: [selectedIndexPath!], with: .fade) 
       self.tblExpandable.reloadData() 
      } 
      self.overlayView.removeFromSuperview() 
    } 

    // Cancel action 
    let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) 

    // Add action 
    ac.addAction(deleteAction) 
    ac.addAction(cancelAction) 

    present(ac, animated: true, completion: nil) 
} 

// MARK: Custom function 
func setUpOverlayView() { 
    overlayView = Overlay(frame: self.view.bounds) 
    overlayView.backgroundColor = UIColor.black 

    let tapGestureRecognizer = UITapGestureRecognizer(target: self, action:#selector(LoopTableViewController.tapGestureRecognized(_:))) 
    tapGestureRecognizer.cancelsTouchesInView = false 

    self.overlayView.addGestureRecognizer(tapGestureRecognizer) 

    overlayViewLayer = CAShapeLayer() 
    overlayViewLayer.frame = overlayView.bounds 
    overlayViewLayer.masksToBounds = true 
} 

func tapGestureRecognized(_ tapGesture: UITapGestureRecognizer) { 
    let selectedIndexPath = openedIndexPath 
    openedIndexPath = nil 
    if selectedIndexPath != nil { 
     self.tblExpandable.reloadRows(at: [selectedIndexPath!], with: .fade) 
    } 
    stopPlayingLoop() 
    UIView.animate(
     withDuration: 0.3, 
     animations: { self.overlayView.alpha = 0.0 }, 
     completion: { finished in self.overlayView.removeFromSuperview()}) 

} 

// MARK: Formatter 
let dateFormatter: DateFormatter = { 
    let formatter = DateFormatter() 
    formatter.dateStyle = .medium 
    formatter.timeStyle = .none 

    return formatter 
}() 

func formatDuration(_ duration: Int) -> String { 
    let hour = duration/3600 
    let minutes = duration/60 
    let seconds = duration % 60 

    return NSString.localizedStringWithFormat("%02d:%02d:%02d", hour, minutes, seconds) as String 
} 

@IBAction func cancelToMainController(_ segue: UIStoryboardSegue){ 

} 


// MARK: Music Player Actions 
@IBAction func playLoopAtTime(_ sender: UISlider) { 
    loopPlayer.pause() 
    loopPlayer.playAt(Double(sender.value)) 
} 

@IBAction func playMusic(_ sender: UIButton) { 
    if sender.currentImage == Images.playButton { 
     playLoop(sender) 
    } else { 
     pauseLoop(sender) 
    } 
} 


// MARK: Segue 
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 
    if segue.identifier == Segue.editExistingLoopIdentifier { 
     if let destination = segue.destination as? EditLoopViewController { 
      destination.loop = loopPlayer.currentLoop 
     } 
    } 
} 
+2

能否請您縮小你的代碼,只是相關部分。 – rmaddy

+1

我不認爲你正在使用更簡單的方式來使用'UITableView'來擴展單元格,你可以避免所有的動畫,添加子視圖等,使用另一種類型的單元格來擴展,例如使用' insertRowsAtIndexPaths:'和'deleteRowsAtIndexPaths:'。我創建了一個小圖書館來展示如何使用它https://github.com/Vkt0r/AccordionMenu –

回答

0

你可以做,只是簡單地使用下面的方法

-insertRowsAtIndexPaths 
-deleteRowsAtIndexPaths 
+0

他還添加了動畫。他想擴大牢房。 – User511