2016-06-13 16 views
7

Tôi mới sử dụng lập trình nhanh và iOS, nhưng tôi đã có thể xây dựng giao diện khởi động chủ yếu ổn định cho ứng dụng của mình trong Xcode. CollectionView lấy một hình ảnh và văn bản từ một loạt các từ điển được tạo từ một tệp csv trên máy chủ mạng gia đình của tôi. Các tập tin cvs chứa dữ liệu theo định dạng sau (lưu ý, url đã được thay đổi để bảo vệ hình ảnh được cấp phép):swift iOS - Hình ảnh UICollectionView được trộn lẫn sau khi cuộn nhanh

tập tin csv

csv file is @ url https://myserver.com/csv.txt and contains the following 

Title";"SeriesImageURL 
Title1";"https://licensedimage.com/url1 
Title2";"https://licensedimage.com/url2 
... 
Title1000";"https://licensedimage.com/url1000 

Vấn đề là khi di chuyển một cách nhanh chóng thông qua các CollectionView, tế bào sẽ lấy hình ảnh không đúng. Đáng chú ý, nếu bạn làm một cuộn chậm hoặc trung bình, một hình ảnh khác sẽ hiển thị trước khi hình ảnh chính xác được đưa vào ô chính xác (văn bản nhãn cho các ô luôn chính xác, chỉ hình ảnh bị tắt). Sau khi sự không phù hợp của hình ảnh vào ô phù hợp với nhãn xảy ra, tất cả các ô khác trong CollectionView cũng sẽ hiển thị hình ảnh không chính xác.

Ví dụ: Ô 1-9 sẽ hiển thị Title1-9 với đúng Image1-9 Khi cuộn chậm, Ô 19-27 sẽ hiển thị Tiêu đề 19-27, sẽ hiển thị nhanh Hình ảnh 10-18 và sau đó hiển thị Hình 19-27 chính xác. Khi di chuyển nhanh chóng một số lượng lớn các ô (ví dụ: từ ô 1-9 đến ô 90-99), các ô 90-99 sẽ hiển thị Tiêu đề 90-99, sẽ hiển thị Hình ảnh 10-50, và sau đó sẽ không chính xác ở lại Hình 41- 50 (hoặc ở xa). Khi di chuyển xa hơn, Ô 100+ sẽ hiển thị Tiêu đề chính xác nhưng sẽ chỉ hiển thị hình ảnh từ phạm vi Hình 41-50.

Tôi nghĩ lỗi này là do việc tái sử dụng ô không được xử lý đúng cách, bộ nhớ đệm của hình ảnh không được xử lý đúng cách hoặc cả hai. Nó cũng có thể là một cái gì đó tôi không nhìn thấy như là một lập trình viên mới bắt đầu iOS/swift. Tôi đã cố gắng để thực hiện một yêu cầu với một bổ trợ hoàn thành nhưng dường như không thể làm cho nó hoạt động đúng với cách mã của tôi được thiết lập. Tôi sẽ đánh giá cao bất kỳ sự giúp đỡ nào với điều này cũng như giải thích cho lý do tại sao sửa chữa hoạt động theo cách của nó. Cảm ơn!

Mã có liên quan bên dưới.

SeriesCollectionViewController.swift

class SeriesCollectionViewController: UICollectionViewController, UISearchBarDelegate { 

let reuseIdentifier:String = "SeriesCell" 

// Set Data Source Models & Variables 
struct seriesModel { 

    let title: AnyObject 
    let seriesimageurl: AnyObject 
} 
var seriesDict = [String:AnyObject]() 
var seriesArray = [seriesModel]() 

// Image Cache 
var imageCache = NSCache() 

override func viewDidLoad() { 
    super.viewDidLoad() 
    // Grab Data from Source 
    do { 

     let url = NSURL(string: "https://myserver.com/csv.txt") 
     let fullText = try NSString(contentsOfURL: url!, encoding: NSUTF8StringEncoding) 
     let readings = fullText.componentsSeparatedByString("\n") as [String] 
     var seriesDictCount = readings.count 
     seriesDictCount -= 1 
     for i in 1..<seriesDictCount { 
      let seriesData = readings[i].componentsSeparatedByString("\";\"") 
      seriesDict["Title"] = "\(seriesData[0])" 
      seriesDict["SeriesImageURL"] = "\(seriesData[1])" 
      seriesArray.append(seriesModel(
       title: seriesDict["Title"]!, 
       seriesimageurl: seriesDict["SeriesImageURL"]!, 
      )) 
     } 
    } catch let error as NSError { 
     print("Error: \(error)") 
    } 
} 

override func didReceiveMemoryWarning() { 
    super.didReceiveMemoryWarning() 
    imageCache.removeAllObjects() 
    // Dispose of any resources that can be recreated. 
} 

//... 
//...skipping over some stuff that isn't relevant 
//... 

override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> SeriesCollectionViewCell { 
    let cell: SeriesCollectionViewCell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! SeriesCollectionViewCell 

    if (self.searchBarActive) { 
     let series = seriesArrayForSearchResult[indexPath.row] 
     do { 
      // set image 
      if let imageURL = NSURL(string: "\(series.seriesimageurl)") { 
       if let image = imageCache.objectForKey(imageURL) as? UIImage { 
         cell.seriesImage.image = image 
       } else { 
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), { 
         if let tvimageData = NSData(contentsOfURL: imageURL) { 
          let image = UIImage(data: tvimageData) 
          self.imageCache.setObject(image!, forKey: imageURL) 
           dispatch_async(dispatch_get_main_queue(), {() -> Void in 
            cell.seriesImage.image = nil 
            cell.seriesImage.image = image 
           }) 
         } 
        }) 
       } 
      } 
      cell.seriesLabel.text = "\(series.title)" 
     } 
    } else { 
     let series = seriesArray[indexPath.row] 
     do { 
      // set image 
      if let imageURL = NSURL(string: "\(series.seriesimageurl)") { 
       if let image = imageCache.objectForKey(imageURL) as? UIImage { 
         cell.seriesImage.image = image 
       } else { 
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), { 
         if let tvimageData = NSData(contentsOfURL: imageURL) { 
          let image = UIImage(data: tvimageData) 
          self.imageCache.setObject(image!, forKey: imageURL) 
          dispatch_async(dispatch_get_main_queue(), {() -> Void in 
           cell.seriesImage.image = nil 
           cell.seriesImage.image = image 
          }) 
         } 
        }) 
       } 

      } 
      cell.seriesLabel.text = "\(series.title)" 
     } 
    } 
    cell.layer.shouldRasterize = true 
    cell.layer.rasterizationScale = UIScreen.mainScreen().scale 
    cell.prepareForReuse() 
    return cell 
} 

SeriesCollectionViewCell

class SeriesCollectionViewCell: UICollectionViewCell { 

@IBOutlet weak var seriesImage: UIImageView! 
@IBOutlet weak var seriesLabel: UILabel! 

} 
+0

Các ô được sử dụng lại và bạn đang gửi đi tìm nạp hình ảnh không đồng bộ, vì vậy khi bạn sử dụng ô và tìm nạp hình ảnh mới không đồng bộ thì tìm nạp cũ vẫn đang chạy. Bạn cần phải xử lý này. Có lẽ cách dễ nhất là sử dụng một cái gì đó SDWebImage trong đó có một phần mở rộng trên UIImageView mà sẽ xử lý này cho bạn – Paulw11

+0

@ Paulw11: Yea, tôi đã tìm thấy nó đã làm với các yêu cầu không được hủy bỏ đúng hoặc một cái gì đó. Tôi đã hy vọng để viết mã chỉ sử dụng UIKit kể từ khi tôi đang sử dụng ứng dụng này như là một kinh nghiệm học tập và tôi cảm thấy như dựa vào một khuôn khổ bên ngoài đánh bại mục đích vì nó là một phím tắt. Tôi đã thực sự nhìn vào Alamofire/AlamofireImage và SDWebImage, mặc dù, nhưng đối với một số lý do sau khi các pod được cài đặt, tôi không thể nhập khẩu trong các mô-đun (đây là một vấn đề riêng biệt tôi đã không hoàn toàn tìm ra). Tôi sẽ sử dụng nó nếu tôi cần mặc dù, chỉ muốn thử xử lý nó với UIKit trước. – shadowofjazz

+0

Cách tiếp cận khác là lưu trữ URL hình ảnh làm thuộc tính của ô của bạn, sau đó trong quá trình đóng hoàn thành, hãy kiểm tra giá trị thuộc tính đối với URL bạn vừa tìm nạp. Nếu nó không khớp thì không đặt hình ảnh vì ô đã được sử dụng lại. – Paulw11

Trả lời

9

Bạn cần phải hiểu làm thế nào dequeue hoạt động đúng. Có những bài viết rất hay cho việc này.

Để tóm tắt:

Để duy trì di chuyển êm ái, dequeue được sử dụng trong đó chủ yếu reuses tế bào sau một giới hạn nhất định. Giả sử bạn có 10 ô hiển thị ở thời gian , có khả năng sẽ tạo ra các ô 16-18 (3-4 ở trên, 3-4 bên dưới, chỉ thô sơ ), mặc dù bạn có thể cần 1000 ô.

Bây giờ khi bạn đang làm this-

let cell: SeriesCollectionViewCell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! SeriesCollectionViewCell 

Bạn đang sử dụng lại một tế bào hiện có từ các tế bào đã được thực hiện. Đó là lý do tại sao bạn thấy hình ảnh cũ trong một thời gian và sau đó bạn sẽ thấy hình ảnh mới sau khi được tải.

Bạn cần xóa hình ảnh cũ ngay khi bạn khử đi ô.

cell.seriesImage.image = UIImage() //nil 

Về cơ bản, bạn sẽ đặt trình giữ chỗ trống theo cách này trong hình ảnh và không gây nhầm lẫn với imageCache là không trong trường hợp của bạn.

Đó giải quyết vấn đề- này

Đáng chú ý, nếu bạn làm một cuộn chậm hoặc trung bình, một hình ảnh khác nhau sẽ hiển thị trước hình ảnh chính xác là ra vào tế bào chính xác (các văn bản nhãn cho các tế bào luôn chính xác, chỉ có hình ảnh được tắt).

Bây giờ khi gán hình ảnh cho ô hiện tại, bạn cần phải kiểm tra lại, nếu hình ảnh bạn đang gán thuộc về ô này hay không. Bạn cần kiểm tra điều này vì khung nhìn hình ảnh bạn đang gán cho nó đang được tái sử dụng và nó có thể được liên kết với một ô tại một indexPath khác với khi mà yêu cầu tải hình ảnh được tạo ra.

Khi bạn dequeue các tế bào và thiết lập các hình ảnh bất động sản để nil, yêu cầu các seriesImage để nhớ indexPath hiện tại cho bạn.

cell.seriesImage.indexPath = indexPath 

Sau đó, bạn chỉ gán hình ảnh cho nó, nếu imageView vẫn thuộc về chỉ mục được chỉ định trước đó. Điều này làm việc 100% với tái sử dụng tế bào.

if cell.seriesImage.indexPath == indexPath 
cell.seriesImage.image = image 

Bạn có thể cần cân nhắc việc đặt indexPath trên phiên bản UIImageView. Đây là điều tôi đã chuẩn bị và sử dụng cho một kịch bản tương tự- UIView-Additions

Có sẵn trong cả hai mục tiêu-C & Swift.

+0

Vì vậy, sau khi một số thử nghiệm, xóa hình ảnh cũ ngay sau khi tôi dequeue các tế bào dường như làm rối loạn bộ nhớ đệm hình ảnh. Nó khắc phục sự cố của nhiều hình ảnh được hiển thị trong một ô trong khi di chuyển, nhưng nó phá vỡ bộ nhớ đệm của hình ảnh đã được cuộn đến. Ví dụ: nếu tôi cuộn lùi các hình ảnh hiện có màu đen và sẽ không được tải ngay cả khi có tín hiệu reloaddata() được gửi qua làm mới. Khi kiểm tra hình ảnh thuộc về ô, bạn có thể mở rộng thêm một chút không?Tôi đã xem xét tập tin nhanh UIView Additions của bạn nhưng tôi không chắc chắn làm thế nào để thực hiện nó cho cấu trúc mã của tôi. – shadowofjazz

+0

Khi bạn dequeue một tế bào, bạn chỉ định indexPath để imageView dụ, ở một giai đoạn sau, bạn kiểm tra giá trị này trước khi gán hình ảnh. Tôi đang cập nhật câu trả lời ngay bây giờ. –

+0

Theo như việc đặt lại hình ảnh được lưu vào bộ nhớ cache chuyển sang màu đen là có liên quan, tôi rất nghi ngờ đó là một vấn đề với cùng một đối tượng được tham chiếu qua imageCache và hình ảnh. Giả sử bạn có một hình ảnh, đã lưu trữ nó, bây giờ cell.seriesImage đã trỏ đến cá thể UIImage này bên trong bộ nhớ cache và chúng ta cần phải thiết lập thủ công nó thành nil. Điều đó cũng thiết lập nil cho instance imageCache được lưu trong bộ nhớ cache vì cả hai đều trỏ đến cùng một cá thể. Điều này có vẻ như loại hacky, Hãy thử thiết lập UIImage() thay vì nil, điều này sẽ thiết lập một trường hợp trống trên hình ảnh với kích thước CGRectZero. Bằng cách này, bạn sẽ không gây rối với các phiên bản đã lưu trong bộ nhớ cache hiện có. –

2

Triển khai func prepareForReuse() trong lớp ô tùy chỉnh của bạn. Và đặt lại hình ảnh của bạn trong hàm prepareForReuse(). do đó, nó sẽ đặt lại chế độ xem hình ảnh trước khi sử dụng lại ô.

+0

Bạn có thể mở rộng về điều này hoặc làm rõ? Khi tôi đặt lại hình ảnh trong phương thức 'func prepareForReuse()' trong lớp ô tùy chỉnh của tôi, tất cả hình ảnh được cuộn qua đều có màu đen (nil) khi tôi cuộn lại các hình ảnh đó. – shadowofjazz

+0

ghi đè func PrepareForReuse() { super.prepareForReuse() yourImageView.image = UIImage() } –

+0

Cảm ơn! Tôi sẽ thử điều này vào cuối tuần này. – shadowofjazz

Các vấn đề liên quan