Swift - saveContext()、tableView、scrollView 和 reloadRows 问题?

Posted

技术标签:

【中文标题】Swift - saveContext()、tableView、scrollView 和 reloadRows 问题?【英文标题】:Swift - saveContext(), tableView, scrollView, and reloadRows issue? 【发布时间】:2020-09-16 17:58:51 【问题描述】:

我有使用NSFetchedResultsController 填充数据的tableView。单击单元格时,它会将您带到该对象的 detailViewController。并且下面两个属性是用prepare(for:)推进的。

var coreDataStack: CoreDataStack!
var selectedGlaze: Glaze?

在 detailView 中,我有 2 个单元格。第一个是包含带有图像数组的滚动视图的单元格:

import UIKit

protocol SwipedRecipeImageViewDelegate: class 
    func recipeImageViewSwiped(_ cell: RecipePhotoTableViewCell, selectInt: Int)


class RecipePhotoTableViewCell: UITableViewCell, UIScrollViewDelegate 
    
    @IBOutlet weak var scrollView: UIScrollView!
    @IBOutlet weak var pageControl: UIPageControl!
    
    // -
    
    var imagesArray: [Data] = []
    var selectedImageData: Int = 0
    
    // -
    weak var delegate: SwipedRecipeImageViewDelegate?
    // -
    
    override func awakeFromNib() 
        super.awakeFromNib()
        self.backgroundColor = .clear
        self.selectionStyle = .none
        
        scrollView.delegate = self
        
        scrollView.isUserInteractionEnabled = false // Allows didSelectAtRow:
        contentView.addGestureRecognizer(scrollView.panGestureRecognizer) // Allows Scrolling
    
    
    
    override func layoutSubviews() 
        super.layoutSubviews()
        setImages()
        setOffsetX(pageNumber: selectedImageData)
    
    
    
    func configureCell(section: Int, row: Int, images: [RecipeImage], arrayInt: Int, delegate: SwipedRecipeImageViewDelegate) 
        self.delegate = delegate
        
        selectedImageData = arrayInt
        
        for image in images 
            guard let imageData = image.recipeImageData else  return 
            imagesArray.append(imageData)
        
    
    
    
    @IBAction func pageChanged(_ sender: UIPageControl) 
        setOffsetX(pageNumber: sender.currentPage)
    
    
    func setOffsetX(pageNumber: Int) 
        
        pageControl.currentPage = pageNumber
        let offsetX = contentView.bounds.width * CGFloat(pageNumber)
        DispatchQueue.main.async 
            UIView.animate(withDuration: 0.2, delay: 0, options: UIView.AnimationOptions.curveEaseOut, animations: 
                self.scrollView.contentOffset.x = offsetX
            , completion: nil)
        
        
    
    
    
    func setImages() 
        
        // Set Page Count:
        pageControl.numberOfPages = imagesArray.count
        // Set Frame For ImageViews + Scroll View:
        for index in 0..<imagesArray.count 
            let imageView = UIImageView()
            imageView.frame.size = contentView.bounds.size
            imageView.frame.origin.x = contentView.bounds.width * CGFloat(index)
            imageView.image = UIImage(data: imagesArray[index])
            imageView.contentMode = .scaleAspectFill
            imageView.layer.masksToBounds = true // Limits Frame Size
            
            scrollView.addSubview(imageView)
        
        
        // Set ScrollView Size:
        scrollView.contentSize = CGSize(width: (contentView.bounds.width * CGFloat(imagesArray.count)), height: contentView.bounds.height)
        scrollView.delegate = self
    
    
    // Set Page Number:
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) 
        let pageNumber = scrollView.contentOffset.x / scrollView.frame.size.width
        self.pageControl.currentPage = Int(pageNumber)
        delegate?.recipeImageViewSwiped(self, selectInt: pageControl.currentPage)
    

第二个单元格包含一个带有一些标签的 stackView,用于显示图像显示的数据。它接受很多参数,然后设置 textColor 并更改一些标签。没什么太令人兴奋的,所以我没有包含代码。

DetailViewController:

 override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
    print("cellForRowAt: ", indexPath)
    switch indexPath.section 
    case sectionImage:  // Section 0:
        guard
            let images = selectedGlaze?.glazeImage,
            let glazeImageSelected = selectedGlaze?.glazeImageSelected // This is a Double
            else  return returnDefaultCell() 
        
        let imageArray = images.allObjects as! [RecipeImage] // Takes NSSet of relational data and changes it into an Array to be passed into the image cell.
        let imageSelected = Int(glazeImageSelected) // Double Converted to Int
        
        switch indexPath.row 
        case 0:
            let cell = returnRecipeImageCell()
            return configureRecipeImageCell(cell: cell, for: indexPath, imagesArray: imageArray, imageSelected: imageSelected)
            
        case 1:
            let cell = returnAtmosphereCell()
            return configureAtmosphereCell(cell: cell, for: indexPath, imagesArray: imageArray, imageSelected: imageSelected)
            
        default: return returnDefaultCell()
        
    

SwipedRecipeImageViewDelegate:

func recipeImageViewSwiped(_ cell: RecipePhotoTableViewCell, selectInt: Int) 
    selectedGlaze?.glazeImageSelected = Double(selectInt)
    coreDataStack.saveContext()
    DispatchQueue.main.async  // 
        self.tableView.beginUpdates() //
        let row1: IndexPath = [0,1] //
        self.tableView.reloadRows(at: [row1], with: .automatic) //
        self.tableView.endUpdates() //
    

问题: 我遇到的问题是在调用recipeImageViewSwiped() 后重新加载要使用正确信息更新的第二个单元格。在这里看到:https://imgur.com/a/fIYfehf

DispatchQueue.main.async 块内的代码处于活动状态时会发生这种情况。当块被注释掉时,会发生这种情况:https://imgur.com/a/fYUVZKH - 这是我所期望的。 (除了 [0,1] 处的单元格未更新)。

具体来说,当 tableView 重新加载行 [0,1] 时,cellForRowAt() 只会在该行 [0,1] 上被调用。但我不确定为什么 [0,0] 处的单元格与图像一起弹回滚动视图中显示的原始图像。

目标: 我的目标是让带有滚动视图的单元格在被滑动后不闪烁。还要保存上下文,以便对象可以保存数组中的哪个图像被选中。然后使用所选图像的新信息更新/重新加载第二个单元格,以便它可以正确更新其标签。

编辑:

在 layoutSubviews() 中删除以下内容会产生以下影响:https://imgur.com/a/vwrZfus - 看起来它大部分都在工作。但仍然有一个奇怪的动画。

override func layoutSubviews() 
    super.layoutSubviews()
    setImages()
    //        setOffsetX(pageNumber: selectedImageData)

编辑 2: 这看起来完全是设置单元格视图的问题。连同布局子视图。

编辑 3:

我在layoutSubviews() 中添加了一个Bool: hasSetLayout 和一个开关,它似乎可以按我的意愿工作。 - 但是,如果有人仍然有任何信息可以帮助我理解这个问题,我将不胜感激。

var hasSetLayout = false

override func layoutSubviews() 
        super.layoutSubviews()
        switch hasSetLayout 
        case false: setImages(selectedPhoto: selectedImageData)
        default: break
        
    

【问题讨论】:

只是猜测:动画很可能是因为这行代码:self.tableView.reloadRows(at: [row1], with: .automatic)。这里,.automatic 表示表格视图正在尝试为您选择重新加载动画。如果您不想要动画,请将.automatic 替换为.none,看看是否有帮助。 您好,谢谢您的回复。问题是带有照片的单元格位于路径[0,0],因此不应重新加载。而且我已经确认只有 [0,1] 处的单元格在调用时真正重新加载。 - 但是,在该单元重新加载后,[0,0] 处的光电单元正在调用layoutSubviews() 3 次。 【参考方案1】:

尝试在没有动画的情况下重新加载行:

UIView.performWithoutAnimation 
    tableView.reloadRows(at: [indexPath], with: .none)

【讨论】:

以上是关于Swift - saveContext()、tableView、scrollView 和 reloadRows 问题?的主要内容,如果未能解决你的问题,请参考以下文章

从 saveContext 中的核心数据错误中恢复

由于 NSSQLiteErrorDomain = 1032,iOS Core Data saveContext 方法不成功

如何在 Swift 中呈现 UI 视图并保留 Tab 栏

同时切换 Tabs 和重置 Nav 的 View Controllers - Swift iOS

如何使用 Xibs 在 Swift 中使用 Tab Bars 进行导航

在Swift中定义[tab]键按下的顺序