BottomSheetUIView 在每次获取数据时关闭(分页)

问题描述 投票:0回答:1

我的 Swift 代码有问题,每次我将新数据获取到 BottomSheet 时,它都会关闭他并将他向下滑动。我希望它保持打开状态,直到我向下滑动他将其关闭。我认为问题出在提取没有失败的代码部分,因为当它失败时,它不会关闭我的底部工作表,它只是停止加载指示器并在控制台中打印错误。但是当 fetch 完成时,它会关闭工作表并显示它。 观看视频 视频显示:https://imgur.com/a/HlolQHg

如果您需要代码的其他部分,请随时询问

BottomSheetUIView:


protocol BottomSheetDelegate: AnyObject {
    func bottomSheetDidScroll(offsetY: CGFloat)
    func didSelectBar(with barId: Int) // Delegate metoda za selekciju bara
}

class BottomSheetUIView: UIView, UITableViewDataSource, UITableViewDelegate {
    weak var delegate: BottomSheetDelegate?
    private var currentPage = 1
        private let itemsPerPage = 10
        private var isFetching = false
        private var allDataFetched = false
    var isExpanded = false
    var sheetMaxHeight: CGFloat = UIScreen.main.bounds.height * 0.9
    private var isFirstFetch = true

    private var barsData: [BarsListingModel] = [] {
        didSet {
            
//            tableView.reloadData()
            mainLabel.text = "Showing 1 - \(barsData.count) of \(barsData.count) results"
        }
    }

    private lazy var handleView: UIView = {
        let view = UIView()
        view.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
        view.layer.cornerRadius = 2.5
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()

    private lazy var mainLabel: UILabel = {
        let label = UILabel()
        label.text = "Showing 1 - \(barsData.count) of \(barsData.count) results"
        label.font = UIFont.systemFont(ofSize: 16)
        label.textColor = .darkGray
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()

    private lazy var tableView: UITableView = {
        let tableView = UITableView()
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.register(UINib(nibName: "BarsTableViewCell", bundle: nil), forCellReuseIdentifier: "BarsTableViewCell")
        tableView.delegate = self
        tableView.dataSource = self
        tableView.separatorStyle = .none
        return tableView
    }()

    private let loader: UIActivityIndicatorView = {
        let loader = UIActivityIndicatorView(style: .large)
        loader.translatesAutoresizingMaskIntoConstraints = false
        loader.backgroundColor = UIColor.clear
        loader.hidesWhenStopped = true
        return loader
    }()
    

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupViewHierarchy()
        setupViewAttributes()
        setupLayout()
        setupLoader()

        fetchBarsData(page: currentPage) { [weak self] bars in
            self?.barsData.append(contentsOf: bars)
        }
    }

    private func setupLoader() {
        loader.color = Colors.primaryColor
    }

    private func showLoader() {
        loader.startAnimating()
        loader.isHidden = false
    }

    private func hideLoader() {
        loader.stopAnimating()
        loader.isHidden = true
    }
    
    private func fetchBarsData(page: Int, completion: @escaping ([BarsListingModel]) -> Void) {
        if isFetching || allDataFetched { return }

        isFetching = true
        showLoader()

        let repository = BarsListingRepository()
        let manager = BarsListingManager(repository: repository, mediaRepository: MediaRepository())

        manager.getBars(page: page, perPage: itemsPerPage) { [weak self] models in
            guard let self = self else { return }

            if models.count < self.itemsPerPage {
                self.allDataFetched = true
            }

            // Prepare new data to update `barsData` only after all fetches are complete
            let newBarsData = self.barsData + models
            self.hideLoader()

            // Update `barsData` and reload the table view without triggering animations
            DispatchQueue.main.async {
                UIView.performWithoutAnimation {
                    self.barsData = newBarsData
                    self.tableView.reloadData()
                }
                self.isFetching = false // Reset fetching state after the update
            }

        } onFail: { [weak self] error in
            guard let self = self else { return }
            print("Error fetching bars: \(error ?? "Unknown error")")
            print("Harun Begic")
            DispatchQueue.main.async {
                self.tableView.reloadData()
                self.isFetching = false
                self.hideLoader()
            }
        }
    }
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let offsetY = scrollView.contentOffset.y
        let contentHeight = scrollView.contentSize.height
        let height = scrollView.frame.size.height

        if offsetY > contentHeight - height {
            let currentYPosition = self.frame.origin.y
            print(currentYPosition)
            currentPage += 1

            fetchBarsData(page: currentPage) { _ in
                // Set the BottomSheet position without animation
                self.frame.origin.y = currentYPosition
                
            }
        }
    }
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    private func setupViewHierarchy() {
        self.addSubview(handleView)
        self.addSubview(mainLabel)
        self.addSubview(loader)
        self.addSubview(tableView)
    }

    private func setupViewAttributes() {
        self.backgroundColor = .white
        self.layer.cornerRadius = 15
        self.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
        self.layer.shadowColor = UIColor.black.cgColor
        self.layer.shadowOpacity = 0.1
        self.layer.shadowOffset = CGSize(width: 0, height: -1)
        self.layer.shadowRadius = 10
    }
    
    private func extractAutoKeywords(from postContent: String) -> String {
            let stopwords = ["the", "and", "is", "in", "of", "to", "for", "on", "a", "an", "with", "it", "by"]
    
            let words = postContent
                .lowercased()
                .components(separatedBy: .punctuationCharacters)
                .joined()
                .components(separatedBy: " ")
            
            
            let filteredWords = words.filter { word in
                !stopwords.contains(word) && word.count > 3
            }
            
            let wordFrequency = filteredWords.reduce(into: [String: Int]()) { counts, word in
                counts[word, default: 0] += 1
            }
            
            let sortedWords = wordFrequency.sorted { $0.value > $1.value }
            let topWords = sortedWords.prefix(5).map { $0.key }
            
            return topWords.joined(separator: "   ")
        }

    private func setupLayout() {
        NSLayoutConstraint.activate([
            handleView.centerXAnchor.constraint(equalTo: self.centerXAnchor),
            handleView.topAnchor.constraint(equalTo: self.topAnchor, constant: 8),
            handleView.widthAnchor.constraint(equalToConstant: 40),
            handleView.heightAnchor.constraint(equalToConstant: 5),

            mainLabel.centerXAnchor.constraint(equalTo: self.centerXAnchor),
            mainLabel.topAnchor.constraint(equalTo: handleView.bottomAnchor, constant: 8),

            loader.centerXAnchor.constraint(equalTo: self.centerXAnchor),
            loader.topAnchor.constraint(equalTo: mainLabel.bottomAnchor, constant: 8),

            tableView.topAnchor.constraint(equalTo: loader.bottomAnchor, constant: 8),
            tableView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
            tableView.bottomAnchor.constraint(equalTo: self.bottomAnchor)
        ])
    }

    // MARK: - UITableViewDataSource

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return barsData.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "BarsTableViewCell", for: indexPath) as! BarsTableViewCell
        let bar = barsData[indexPath.row]

        cell.barName.text = bar.postTitle
        cell.barDescription.text = bar.postContent
        cell.barCategory.text = self.extractAutoKeywords(from: bar.postContent)

        if let imageUrlString = bar.imageUrl, let imageUrl = URL(string: imageUrlString) {
            DispatchQueue.global().async {
                if let imageData = try? Data(contentsOf: imageUrl) {
                    DispatchQueue.main.async {
                        cell.barImage.image = UIImage(data: imageData)
                    }
                }
            }
        } else {
            cell.barImage.image = nil
        }

        return cell
    }

    // MARK: - UITableViewDelegate

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let selectedBar = barsData[indexPath.row]
        delegate?.didSelectBar(with: selectedBar.id)
    }

    override func layoutSubviews() {
        if isExpanded {
            self.frame.origin.y = UIScreen.main.bounds.height - sheetMaxHeight
        }
    }
    //    func scrollViewDidScroll(_ scrollView: UIScrollView) {
//        delegate?.bottomSheetDidScroll(offsetY: scrollView.contentOffset.y)
//    }
}
swift swiftui pagination
1个回答
0
投票

防止底层工作表在 Swift 中获取数据期间关闭

问题

您遇到一个问题,每次在分页期间获取新数据时,Swift 中的自定义

BottomSheetUIView
都会关闭并向下滑动。所需的行为是底部工作表保持打开状态,直到用户通过向下滑动手动将其关闭。

分析

根据提供的代码和描述,问题可能源于获取新数据后视图的更新方式。该问题专门发生在获取成功时,而不是失败时。

解决方案

为了防止底部工作表在获取数据期间关闭,我们需要修改接收新数据后视图的更新方式。以下是解决此问题的步骤:

  1. 更新期间保持底部工作表的当前位置。
  2. 使用
    UIView.performWithoutAnimation
    更新表格视图而不触发布局更改。
  3. 确保
    BottomSheetUIView
    的框架在更新过程中没有被重置。

这是修改后的

fetchBarsData
函数:

private func fetchBarsData(page: Int, completion: @escaping ([BarsListingModel]) -> Void) {
    if isFetching || allDataFetched { return }

    isFetching = true
    showLoader()

    let repository = BarsListingRepository()
    let manager = BarsListingManager(repository: repository, mediaRepository: MediaRepository())

    manager.getBars(page: page, perPage: itemsPerPage) { [weak self] models in
        guard let self = self else { return }

        if models.count < self.itemsPerPage {
            self.allDataFetched = true
        }

        // Capture the current position
        let currentPosition = self.frame.origin.y

        // Prepare new data to update `barsData` only after all fetches are complete
        let newBarsData = self.barsData + models
        self.hideLoader()

        // Update `barsData` and reload the table view without triggering animations
        DispatchQueue.main.async {
            UIView.performWithoutAnimation {
                self.barsData = newBarsData
                self.tableView.reloadData()
                
                // Ensure the bottom sheet stays at its current position
                self.frame.origin.y = currentPosition
            }
            self.isFetching = false // Reset fetching state after the update
        }

    } onFail: { [weak self] error in
        guard let self = self else { return }
        print("Error fetching bars: \(error ?? "Unknown error")")
        DispatchQueue.main.async {
            self.isFetching = false
            self.hideLoader()
        }
    }
}

说明

  1. 我们在更新数据之前捕获底部工作表的当前位置。
  2. 我们使用
    UIView.performWithoutAnimation
    来防止更新期间出现任何不需要的动画。
  3. 更新数据并重新加载表格视图后,我们将底部工作表的位置显式设置回原来的位置。

其他注意事项

  1. 布局更新:确保代码的其他部分在数据获取期间或之后没有强制更新布局或更改

    BottomSheetUIView
    的框架。

  2. 滚动视图委托:您的

    scrollViewDidScroll(_:)
    方法当前已被注释掉。如果您需要跟踪滚动,请确保它不会干扰工作表的位置。

  3. 展开/折叠逻辑:仔细检查视图控制器中的任何展开/折叠逻辑,以确保在数据更新期间不会无意中触发它。

  4. 自动布局:如果您使用自动布局,请确保约束不会在更新期间强制工作表到不同的位置。

调试技巧

  1. 添加打印语句或断点以跟踪数据更新前后工作表的位置。
  2. 实现
    UIScrollViewDelegate
    方法
    scrollViewDidScroll(_:)
    来记录位置更改并识别任何意外滚动。
func scrollViewDidScroll(_ scrollView: UIScrollView) {
    print("Bottom sheet position: \(self.frame.origin.y)")
    // Your existing scroll view logic
}

通过实施这些更改并考虑其他要点,您的底部工作表应在数据获取和分页期间保持不变。

© www.soinside.com 2019 - 2024. All rights reserved.