Swift3:UITableView嵌套UICollectionView首次点击传值为nil求助
嘿,这个问题我一眼就看穿了——你在点击事件里的执行顺序搞反啦!咱们一步步拆解修复:
问题根源
你当前在collectionView(_:didSelectItemAt:)里的代码逻辑是先触发跳转的performSegue(也就是调用didSelectAction!()),然后才给vcInstance的musicName和musicUrl赋值。但performSegue会立刻触发prepare(for:sender:)方法,这时候你的两个属性还没被赋值,所以首次点击时传递给播放页面的就是nil;第二次点击时,因为第一次点击已经把属性填上了,所以能拿到值,但这纯属巧合,逻辑是完全错误的。
解决方案1:快速调整执行顺序(最省心的临时修复)
只需要把赋值代码放到触发didSelectAction之前,这样跳转的时候属性已经有正确的值了:
修改CategoryRow里的collectionView(_:didSelectItemAt:)方法:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { // 先给ViewController的属性赋值 let trackName = RemoteModel.sharedInstanceRemoteModel.singleGuidedTrandingTrackname[indexPath.row] let trackUrl = RemoteModel.sharedInstanceRemoteModel.singleGuidedTrandingTrackurl[indexPath.row] vcInstance?.musicName = trackName vcInstance?.musicUrl = trackUrl // 再触发跳转动作 didSelectAction!() }
解决方案2:更优雅的传值方式(推荐长期使用)
上面的方法虽然能解决问题,但依赖ViewController的属性还是有点耦合。更靠谱的方式是直接把选中的音乐数据通过闭包参数传递,再作为sender传给segue,完全避免属性依赖:
步骤1:修改CategoryRow的闭包定义
把原来无参数的闭包改成带音乐数据的:
class CategoryRow : UITableViewCell { @IBOutlet var collectionView: UICollectionView! var vcInstance: newTableViewController? var parentViewController: UIViewController? = nil // 修改闭包类型,添加两个参数 var didSelectAction: ((String, String) -> Void )? }
步骤2:更新CollectionView点击事件
在collectionView(_:didSelectItemAt:)里直接传递选中的数据:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { let trackName = RemoteModel.sharedInstanceRemoteModel.singleGuidedTrandingTrackname[indexPath.row] let trackUrl = RemoteModel.sharedInstanceRemoteModel.singleGuidedTrandingTrackurl[indexPath.row] // 把数据传给闭包 didSelectAction?(trackName, trackUrl) }
步骤3:在TableViewController中接收数据并跳转
修改cellForRowAt里的闭包实现,同时可以封装一个数据结构来传递:
// 先在newTableViewController里定义一个简单的结构体,用来封装音乐数据 struct MusicTrack { let name: String let url: String } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell : CategoryRow = tableView.dequeueReusableCell(withIdentifier: "cell")as! CategoryRow cell.vcInstance = self cell.parentViewController = self cell.didSelectAction = { [weak self] trackName, trackUrl in guard let self = self else { return } let musicTrack = MusicTrack(name: trackName, url: trackUrl) // 把封装好的数据作为sender传递给segue self.performSegue(withIdentifier: "homePlayer", sender: musicTrack) } return cell }
步骤4:在prepareForSegue中接收数据
直接从sender里获取音乐数据,不用依赖ViewController的属性:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.identifier == "homePlayer" { let playerVC = segue.destination as! PlayerViewController // 从sender里解析出音乐数据 if let musicTrack = sender as? MusicTrack { playerVC.UrlAudio = musicTrack.url playerVC.songTittle = musicTrack.name } } }
这样修改后,不仅彻底解决了首次点击的nil问题,代码的耦合度也更低,后续维护起来会方便很多。
内容的提问来源于stack exchange,提问作者Chetan Lodhi

