我的 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 中的自定义
BottomSheetUIView
都会关闭并向下滑动。所需的行为是底部工作表保持打开状态,直到用户通过向下滑动手动将其关闭。
根据提供的代码和描述,问题可能源于获取新数据后视图的更新方式。该问题专门发生在获取成功时,而不是失败时。
为了防止底部工作表在获取数据期间关闭,我们需要修改接收新数据后视图的更新方式。以下是解决此问题的步骤:
UIView.performWithoutAnimation
更新表格视图而不触发布局更改。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()
}
}
}
UIView.performWithoutAnimation
来防止更新期间出现任何不需要的动画。布局更新:确保代码的其他部分在数据获取期间或之后没有强制更新布局或更改
BottomSheetUIView
的框架。
滚动视图委托:您的
scrollViewDidScroll(_:)
方法当前已被注释掉。如果您需要跟踪滚动,请确保它不会干扰工作表的位置。
展开/折叠逻辑:仔细检查视图控制器中的任何展开/折叠逻辑,以确保在数据更新期间不会无意中触发它。
自动布局:如果您使用自动布局,请确保约束不会在更新期间强制工作表到不同的位置。
UIScrollViewDelegate
方法 scrollViewDidScroll(_:)
来记录位置更改并识别任何意外滚动。func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("Bottom sheet position: \(self.frame.origin.y)")
// Your existing scroll view logic
}
通过实施这些更改并考虑其他要点,您的底部工作表应在数据获取和分页期间保持不变。