In my UITableViewCell I have a UIImageView that download image asynchronously (with Kingfisher library). It's very similar to Instagram.
I'm using autolayout and I want to use UITableView.automaticDimension for row height. How should I set constraints so that the height of the cell becomes taller or shorter according to the image? If I set an initial constraint for the UIImageView height and change it once I download the image, I have at leat one element that changes its height to "fill" the space added (or removed) by the UIImageView.
Calculate the aspect ratio preserved height using with actual size of the images and device screen sizes for each one, and update UIImageView height constraint on the UITableViewCell.
Just call feedCell.configure(with: FeedImage) on the tableView(_:cellForRowAt:)
// FeedImage data will be load from your API, in the best practices, you should get actual sizes of the image from your API
struct FeedImage {
var url: String = ""
var width: CGFloat = 0.0
var height: CGFloat = 0.0
}
class FeedCell: UITableViewCell {
#IBOutlet weak var feedImageView: UIImageView!
#IBOutlet weak var feedImageViewHeightConstraint: NSLayoutConstraint!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func configure(with feedImage: FeedImage) {
feedImageViewHeightConstraint.constant = getAspectRatioPreservedHeight(actualWidth: feedImage.width, actualHeight: feedImage.height)
feedImageView.loadImage(with: feedImage.url) // Asynchronous image downloading and setting, you can use any library such as SDWebImage, AlamofireImage, Kingfisher or your custom asynchronous image downloader
}
// Calculate the aspect ratio preserved height using with actual size of the images and device screen sizes.
private func getAspectRatioPreservedHeight(actualWidth: CGFloat, actualHeight: CGFloat) -> CGFloat {
let WIDTH = UIScreen.main.bounds.width
let HEIGHT = UIScreen.main.bounds.height
if (actualWidth == 0) { return CGFloat(400.0) }
let newComputedHeight = (WIDTH*actualHeight)/actualWidth
if newComputedHeight > (HEIGHT * 0.6) {
return (HEIGHT * 0.6)
}
return newComputedHeight
}
}
you have to Crop Image Propotionally and than save to KingFisher Cache.
let cached = ImageCache.default.imageCachedType(forKey: url)
if cached != .none {
getImageFromCache(key: url) { [weak self] (cachedImage) in
self?.image = cachedImage
self?.completionHandler?(cachedImage)
}
}
else {
getImageFromServer(key: url) { [weak self] (cahcedImage) in
self?.image = cahcedImage
ImageCache.default.store(cahcedImage, forKey: self!.url)
self?.completionHandler?(cahcedImage)
}
}
func getImageFromCache(key:String,complition:#escaping (UIImage)->()) {
ImageCache.default.retrieveImage(forKey: url, options: nil) { [weak self] (image, cachetype) in
guard let cachedImage = image , let opCancelled = self?.isCancelled , opCancelled == false else {
return
}
complition(cachedImage)
}
}
func getImageFromServer(key:String,complition:#escaping (UIImage)->()) {
if let imageUrl = URL(string: key) {
KingfisherManager.shared.retrieveImage(with: imageUrl, options: nil, progressBlock: nil) { [weak self] (image, error, cachetype, _ ) in
if error == nil {
guard let cachedImage = image , let opCancelled = self?.isCancelled , opCancelled == false else {
return
}
let finalheight = (UIScreen.main.bounds.width * CGFloat(cachedImage.size.height)) / CGFloat(cachedImage.size.width)
let newImage = cachedImage.resize(targetSize: CGSize(width: UIScreen.main.bounds.width, height: finalheight))
complition(newImage)
}
else {
print("Error ###### == Errror While Downloading Image")
}
}
}
else {
print("Error ###### == can't create Url of String")
}
}
Related
I am trying to display IPFS image in swift using UIImageView
. When I run my code in xCode/Simulator the image shows fine.
. When I run my code in Xcode/My iPhone device connected via USB works. I do see the image
But when run the app on my phone (after app is installed via Xcode) the IPFS image is not shows for the UIImageView - what am I doing wrong?
func tableView(_ myTableViewAccount: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: TableViewCellAccount = myTableViewAccount.dequeueReusableCell(withIdentifier: "aCell") as! TableViewCellAccount
guard (self.accountsArray.count > 0) else { return cell }
print("accountArray=\(self.accountsArray) indexPath=\(indexPath.row)")
let count = self.accountsArray.count
if (indexPath.row < count) {
guard (self.accountsArray[indexPath.row].name != nil) else { return cell }
cell.accountImgView?.frame.size = CGSize(width: 50, height: 50)
cell.accountImgView.center = view.center
cell.accountImgView.layer.cornerRadius = 18
cell.accountImgView?.clipsToBounds = true
let activityIndicator = UIActivityIndicatorView()
activityIndicator.frame = cell.accountImgView.bounds
cell.accountImgView.addSubview(activityIndicator)
activityIndicator.backgroundColor = UIColor.white
activityIndicator.startAnimating()
let front_img_url = "https://cloudflare-ipfs.com/ipfs/QmYFDgVBMrRfEm5JpVSWeSDAfTUpboEiL8rZyGym24MNVu"
let frontImageURL = URL(string: front_img_url)
if frontImageURL != nil {
DispatchQueue.main.async {
let dataProdFrontImg = try? Data(contentsOf: frontImageURL!)
if let data_front_img = dataProdFrontImg { //fromn img
activityIndicator.stopAnimating()
activityIndicator.removeFromSuperview()
let accountImage = UIImage(data: data_front_img)
cell.accountImgView.image = accountImage
}
}
}
}
return cell
}
... Cell Table is defined like this
class TableViewCellAccount: UITableViewCell {
#IBOutlet weak var accountImgView: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
I moved away from UIImage View instead, I used
#IBOutlet weak var accountImgView: WKWebView?
that fixed my issue
I looked at several answers for this problem but none helped.
vc1 is a regular vc, I grab 20 images from firebase, in vc2 I use ARKit to display those images upon user selection. I have a collectionView in vc2 that paginates 20 more images from firebase. The problem is when the next 20 images are loading and I start scrolling, the app crashes with Message from debugger: Terminated due to memory issue. When scrolling those new images, I look at the memory graph and it shoots up to 1 gig, so that's the reason for the crash. ARKit and the nodes I have floating around also contribute to the memory bump but they are not the reason for the crash as stated below.
1- Inside the cell I use SDWebImage to display the image inside the imageView. Once I comment out SDWebImage everything works, scrolling is smooth, and no more crashes but of course I can't see the image. I switched to URLSession.shared.dataTask and the same memory issue reoccurs.
2- The images were initially taken with the iPhone full screen camera and saved with jpegData(compressionQuality: 0.3). The cell size is 40x40. Inside the the SDWebImage completion block I tried to resize the image but the memory crash still persists.
3- I used Instruments > Leaks to look for memory leaks and a few Foundation leaks appeared but when I dismiss vc2 Deinit always runs. Inside vc2 there aren't any long running timers or loops and I use [weak self] inside all of the closures.
4- As I stated in the second point the problem is definitely the imageView/image because once I remove it from the process everything works fine. If I don't show any images everything works fine (40 red imageViews with no images inside of them will appear).
What can I do to fix this issue?
Paginating to pull more images
for child in snapshot.children.allObjects as! [DataSnapshot] {
guard let dict = child.value as? [String:Any] else { continue }
let post = Post(dict: dict)
datasource.append(post)
let lastIndex = datasource.count - 1
let indexPath = IndexPath(item: lastIndex, section: 0)
UIView.performWithoutAnimation {
collectionView.insertItems(at: [indexPath])
}
}
cellForItem:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: self.cellId, for: indexPath) as! PostCell
cell.resetAll()
cell.post = dataSource[indexPath.item]
return cell
}
PostCell
private lazy var imageView: UIImageView = {
let iv = UIImageView()
iv.translatesAutoresizingMaskIntoConstraints = false
iv.contentMode = .scaleAspectFill
iv.layer.cornerRadius = 15
iv.layer.masksToBounds = true
iv.backgroundColor = .red
iv.isHidden = true
return iv
}()
private lazy var spinner: UIActivityIndicatorView = {
let actIndi = UIActivityIndicatorView(style: UIActivityIndicatorView.Style.medium) // ...
}()
var post: Post? {
didSet {
guard let urlSr = post?.urlStr, let url = URL(string: urlSr) else { return }
spinner.startAnimating()
// if I comment this out everything works fine
imageView.sd_setImage(with: url, placeholderImage: placeHolderImage, options: [], completed: {
[weak self] (image, error, cache, url) in
// in here I tried resizing the image but no diff
DispatchQueue.main.async { [weak self] in
self?.showAll()
}
})
setAnchors()
}
}
func resetAll() {
spinner.stopAnimating()
imageView.image = nil
imageView.removeFromSuperView()
imageView.isHidden = true
}
func showAll() {
spinner.stopAnimating()
imageView.isHidden = false
}
func setAnchors() {
contentView.addSubview(imageView)
imageView.addSubview(cellSpinner)
// imageView is pinned to all sides
}
In the comments #matt was 100% correct , the problem was the images were too large for the collectionView at size 40x40, I needed to resize the images. For some reason the resize link in my question didn't work so I used this to resize image instead. I also used URLSession and this answer
I did it in 10 steps, all commented out
// 0. set this anywhere outside any class
let imageCache = NSCache<AnyObject, AnyObject>()
PostCell:
private lazy var imageView: UIImageView = { ... }()
private lazy var spinner: UIActivityIndicatorView = { ... }()
// 1. override prepareForReuse, cancel the task and set it to nil, and set the imageView.image to nil so that the wrong images don't appear while scrolling
override func prepareForReuse() {
super.prepareForReuse()
task?.cancel()
task = nil
imageView.image = nil
}
// 2. add a var to start/stop a urlSession when the cell isn't on screen
private var task: URLSessionDataTask?
var post: Post? {
didSet {
guard let urlStr = post?.urlStr else { return }
spinner.startAnimating()
// 3. crate the new image from this function
createImageFrom(urlStr)
setAnchors()
}
}
func createImageFrom(_ urlStr: String) {
if let cachedImage = imageCache.object(forKey: urlStr as AnyObject) as? UIImage {
// 4. if the image is in cache call this function to show the image
showAllAndSetImageView(with: cachedImage)
} else {
guard let url = URL(string: urlStr) else { return }
// 5. if the image is not in cache start a urlSession and initialize it with the task variable from step 2
task = URLSession.shared.dataTask(with: url, completionHandler: {
[weak self](data, response, error) in
if let error = error { return }
if let response = response as? HTTPURLResponse {
print("response.statusCode: \(response.statusCode)")
guard 200 ..< 300 ~= response.statusCode else { return }
}
guard let data = data, let image = UIImage(data: data) else { return }
// 6. add the image to cache
imageCache.setObject(image, forKey: photoUrlStr as AnyObject)
DispatchQueue.main.async { [weak self] in
// 7. call this function to show the image
self?.showAllAndSetImageView(with: image)
}
})
task?.resume()
}
}
func showAllAndSetImageView(with image: UIImage) {
// 8. resize the image
let resizedImage = resizeImageToCenter(image: image, size: 40) // can also use self.frame.height
imageView.image = resizeImage
showAll()
}
// 9. func to resize the image
func resizeImageToCenter(image: UIImage, size: CGFloat) -> UIImage {
let size = CGSize(width: size, height: size)
// Define rect for thumbnail
let scale = max(size.width/image.size.width, size.height/image.size.height)
let width = image.size.width * scale
let height = image.size.height * scale
let x = (size.width - width) / CGFloat(2)
let y = (size.height - height) / CGFloat(2)
let thumbnailRect = CGRect.init(x: x, y: y, width: width, height: height)
// Generate thumbnail from image
UIGraphicsBeginImageContextWithOptions(size, false, 0)
image.draw(in: thumbnailRect)
let thumbnail = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return thumbnail!
}
func resetAll() { ... }
func showAll() { ... }
func setAnchors() { ... }
I am trying to make a view controller that has a scroll view and text view. I want my scrolling is enabled only when the text content of the text view is a lot. because it seems ugly if I can scroll my view controller even though the text content in the text view is not to much like the picture below
as we can see there is a big gap to the bottom of view controller. this is the code I use for this VC
import UIKit
class NotificationDetailVC: UIViewController {
#IBOutlet weak var notificationTitleLabel: UILabel!
#IBOutlet weak var notificationImage: UIImageView!
#IBOutlet weak var notificationDateLabel: UILabel!
#IBOutlet weak var notificationContentTextView: UITextView!
var dataNotification : [String:Any]?
override func viewDidLoad() {
super.viewDidLoad()
updateUI()
updateReadStatusInUserDefault()
}
#IBAction func imageDidTapped(_ sender: Any) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let popup = storyboard.instantiateViewController(withIdentifier: "imageDisplay") as! ReusableImageDisplayVC
popup.photo = notificationImage.image
self.present(popup, animated: true, completion: nil)
}
func updateUI() {
guard let dataNotification = dataNotification else {return}
notificationTitleLabel.text = dataNotification["Judul"] as? String
notificationContentTextView.text = dataNotification["Keterangan"] as? String
guard let notifDate = dataNotification["TglNotif"] as? String else {return}
notificationDateLabel.text = DateTimeService.changeFormat(of: notifDate, toFormat: "d MMM YYY")
let imagePath = dataNotification["Photo"] as? String
guard let encodedImagePath = imagePath?.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) else {return}
if imagePath != nil {
if imagePath!.isEmpty {
notificationImage.removeFromSuperview()
} else {
notificationImage.sd_setImage(with: URL(string: encodedImagePath), placeholderImage: UIImage(named: "3-2 Img"), options: [.continueInBackground, .progressiveDownload])
}
} else {
notificationImage.removeFromSuperview()
}
}
func updateReadStatusInUserDefault() {
guard let dataNotification = dataNotification,
let notifID = dataNotification["notif_id"] as? Int else {return}
guard var readNotificationStatusUserDefault = UserDefaults.standard.object(forKey: "readNotification") as? [String:String] else {return}
readNotificationStatusUserDefault["\(notifID)"] = "1"
UserDefaults.standard.set(readNotificationStatusUserDefault, forKey: "readNotification")
UserDefaults.standard.synchronize()
}
}
First you need to find size of text of your textview,
you can find size of text using :
func rectForText(text: String, font: UIFont, maxSize: CGSize) -> CGSize
{
let attrString = NSAttributedString.init(string: yourtext, attributes: [NSAttributedStringKey.font:font])
let rect = attrString.boundingRect(with: maxSize, options: NSStringDrawingOptions.usesLineFragmentOrigin, context: nil)
let size = CGSize(width: rect.size.width, height: rect.size.height)//(, )
return size
}
after that set size of your textview according to your text size :
let contentSize : CGSize = rectForText(text: str, font:UIFont.boldSystemFont(ofSize: 15.0) , maxSize : CGSize(width: 200 * (SCREEN_WIDTH / 320), height: 20000) )
and set frame of your textview
var frame = self.myTextView.frame
frame.size.height = contentSize.height
self.myTextView.frame = frame
after set set contentsize of your scrollview
scrollView.contentSize = CGSize(width: self.view.frame.size.width, height: myTextView.frame.size.height + 8)
Hope this help! Happy coding :)
Depending upon the contentSize of text view, adjust the scrollable property of scroll view.
After setting the text on textview, check as below:-
let height = self.notificationContentTextView.contentSize.height
if(height > scrollView.frame.size.height){
scrollView.isScrollEnabled == true
}else{
scrollView.isScrollEnabled == false
}
I'm basically having the same issue as this question:
Slow scroll on UITableView images
My UITableView contains a UIImageView that is 87x123 size.
When my UITableViewController is loaded, it first calls a function that loops through an array of images. These images are high resolutions stored from the photo library. In each iteration, it retrieves the image and resize each image down to 87x123, then stores it back into the original image in the array.
When all the images has been resized and stored, it calls self.tableView.reloadData to populate the data in the array into the cells.
However, like the mentioned question, my UITablView is choppy and lags if I scroll fast before all the images has been resized and stored in the array.
Here's the problematic code:
extension UIImage
{
func resizeImage(originalImage: UIImage, scaledTo size: CGSize) -> UIImage
{
// Avoid redundant drawing
if originalImage.size.equalTo(size)
{
return originalImage
}
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
originalImage.draw(in: CGRect(x: CGFloat(0.0), y: CGFloat(0.0), width: CGFloat(size.width), height: CGFloat(size.height)))
let image = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}
}
func loadImages()
{
DispatchQueue.global(qos: .background).async {
for index in 0..<self.myArray.count
{
if let image = self.myArray[index].image
{
self.myArray[index].image = image.resizeImage(originalImage: image, scaledTo: CGSize(width: 87, height: 123) )
}
if index == self.myArray.count - 1
{
print("FINISHED RESIZING ALL IMAGES")
}
}
}
tableView.reloadData()
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
...
// Size is 87x123
let thumbnailImage = cell.viewWithTag(1) as! UIImageView
DispatchQueue.main.async{
thumbnailImage.image = self.myArray[indexPath.row]
}
thumbnailImage.contentMode = UIViewContentMode.scaleAspectFill
thumbnailImage.layer.borderColor = UIColor.black.cgColor
thumbnailImage.layer.borderWidth = 1.0
thumbnailImage.clipsToBounds = true
return cell
}
I know to do any Non-UI operations in the background thread, which is what I do. Then all I do in cellForRowAt is load the image into the cell using its indexPath.row.
The problem is, as previously mentioned, if I start to scroll the UITableView BEFORE FINISHED RESIZING ALL IMAGES is printed out, i.e. before all the images has been resized, there is noticeable lag and slowness.
However, if I wait UNTIL all the images has been resized and FINISHED RESIZING ALL IMAGES is called before scrolling the UITableView, the scrolling is smooth without any lags.
I can put a loading indicator and have the user wait until all images has been resized and loaded into the cells before having user interaction, but that would be an annoyance since it takes about 8 seconds to resize all the high-res images (18 images to resize).
Is there a better way I can fix this lag?
UPDATE: Following #iWheelBuy's second example, I've implemented the following:
final class ResizeOperation: Operation {
private(set) var image: UIImage
let index: Int
let size: CGSize
init(image: UIImage, index: Int, size: CGSize) {
self.image = image
self.index = index
self.size = size
super.init()
}
override func main() {
image = image.resizeImage(originalImage: image, scaledTo: size)
}
}
class MyTableViewController: UITableViewController
{
...
lazy var resizeQueue: OperationQueue = self.getQueue()
var myArray: [Information] = []
internal struct Information
{
var title: String?
var image: UIImage?
init()
{
}
}
override func viewDidLoad()
{
....
loadImages()
...
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
...
// Size is 87x123
let thumbnailImage = cell.viewWithTag(1) as! UIImageView
DispatchQueue.main.async{
thumbnailImage.image = self.myArray[indexPath.row].image
}
thumbnailImage.contentMode = UIViewContentMode.scaleAspectFill
return cell
}
func loadImages()
{
let size = CGSize(width: 87, height: 123)
for item in myArray
{
let operations = self.myArray.enumerated().map({ ResizeOperation(image: item.image!, index: $0.offset, size: size) })
operations.forEach { [weak queue = resizeQueue, weak controller = self] (operation) in
operation.completionBlock = { [operation] in
DispatchQueue.main.async { [image = operation.image, index = operation.index] in
self.update(image: image, index: index)
}
}
queue?.addOperation(operation)
}
}
}
func update(image: UIImage, index: Int)
{
myArray[index].image = image
tableView.reloadRows(at: [IndexPath(row: index, section: 0)], with: UITableViewRowAnimation.fade)
}
}
However, upon calling tableView.reloadRows, I receive a crash with the error:
attempt to delete row 0 from section 0, but there are only 0 sections
before the update
I'm a bit confused on what it means and how to resolve it.
It is hard to determine the reason why you have lags. But there are some thoughts that might help you to make you code more performant.
Try using some small images at start and see, if it is the size of original image that influences bad performance. Also try to hide these lines and see if anything changes:
// thumbnailImage.contentMode = UIViewContentMode.scaleAspectFill
// thumbnailImage.layer.borderColor = UIColor.black.cgColor
// thumbnailImage.layer.borderWidth = 1.0
// thumbnailImage.clipsToBounds = true
Your code is a little bit oldschool, for loop in loadImages can become more readable with just a few lines of code by applying map or forEach to your image array.
Also, about your array of images. You read it from main thread and modify it from background thread. And you do it simultaneously. I'd suggest to make only image resizing on background... unless you are sure there will be no bad consequences
Check the code example #1 below how you current code can look like.
On the other hand, you can go some other way. For example you can set some placeholder image at start and update cells when image for some specific cell is ready. Not all images at once! If you go with some serial queue, you will get image updates every 0.5 seconds and the UI updates will be fine.
Check the code example #2. It wasn't tested, just to show the way you can go.
Btw, have you tried changing QualityOfService from background to userInitiated? It might decrease resizing time... or not (:
DispatchQueue.global(qos: .userInitiated).async {
// code
}
Example #1
extension UIImage {
func resize(to size: CGSize) -> UIImage {
guard self.size.equalTo(size) else { return self }
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
draw(in: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
let image = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}
static func resize(images: [UIImage], size: CGSize, completion: #escaping ([UIImage]) -> Void) {
DispatchQueue.global(qos: .background).async {
let newArray = images.map({ $0.resize(to: size) })
DispatchQueue.main.async {
completion(newArray)
}
}
}
}
final class YourController: UITableViewController {
var myArray: [UIImage] = []
override func viewDidLoad() {
super.viewDidLoad()
self.loadImages()
}
}
fileprivate extension YourController {
func loadImages() {
UIImage.resize(images: myArray, size: CGSize(width: 87, height: 123)) { [weak controller = self] (newArray) in
guard let controller = controller else { return }
controller.myArray = newArray
controller.tableView.reloadData()
}
}
}
Example #2
final class ResizeOperation: Operation {
private(set) var image: UIImage
let index: Int
let size: CGSize
init(image: UIImage, index: Int, size: CGSize) {
self.image = image
self.index = index
self.size = size
super.init()
}
override func main() {
image = image.resize(to: size)
}
}
final class YourController: UITableViewController {
var myArray: [UIImage] = []
lazy var resizeQueue: OperationQueue = self.getQueue()
override func viewDidLoad() {
super.viewDidLoad()
self.loadImages()
}
private func getQueue() -> OperationQueue {
let queue = OperationQueue()
queue.qualityOfService = .background
queue.maxConcurrentOperationCount = 1
return queue
}
}
fileprivate extension YourController {
func loadImages() {
let size = CGSize(width: 87, height: 123)
let operations = myArray.enumerated().map({ ResizeOperation(image: $0.element, index: $0.offset, size: size) })
operations.forEach { [weak queue = resizeQueue, weak controller = self] (operation) in
operation.completionBlock = { [operation] in
DispatchQueue.main.async { [image = operation.image, index = operation.index] in
controller?.update(image: image, index: index)
}
}
queue?.addOperation(operation)
}
}
func update(image: UIImage, index: Int) {
myArray[index] = image
tableView.reloadRows(at: [IndexPath(row: index, section: 0)], with: UITableViewRowAnimation.fade)
}
}
Recently I found sometimes my gif image not shown. So I write a test code for it:
import UIKit
import ImageIO
extension UIImage {
static func gifImageArray(data: NSData) -> [UIImage] {
let source = CGImageSourceCreateWithData(data, nil)!
let count = CGImageSourceGetCount(source)
if count <= 1 {
return [UIImage(data: data)!]
} else {
var images = [UIImage](); images.reserveCapacity(count)
for i in 0..<count {
let image = CGImageSourceCreateImageAtIndex(source, i, nil)!
images.append( UIImage(CGImage: image) )
}
return images;
}
}
static func gifImage(data: NSData) -> UIImage? {
let gif = gifImageArray(data)
if gif.count <= 1 {
return gif[0]
} else {
return UIImage.animatedImageWithImages(gif, duration: NSTimeInterval(gif.count) * 0.1)
}
}
}
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
let _gifData : NSData = NSData(contentsOfURL: NSURL(string: "https://upload.wikimedia.org/wikipedia/commons/d/d3/Newtons_cradle_animation_book_2.gif")!)!
lazy var _gifImageArray : [UIImage] = UIImage.gifImageArray(self._gifData)
lazy var _gifImage : UIImage = UIImage.gifImage(self._gifData)!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let table = UITableView(frame: self.view.bounds, style: .Plain)
table.dataSource = self
table.delegate = self
self.view.addSubview(table)
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1000;
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1;
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let identifier = "cell"
var cell : UITableViewCell! = tableView.dequeueReusableCellWithIdentifier(identifier);
if cell == nil {
cell = UITableViewCell(style: .Default, reuseIdentifier: identifier)
}
if let imageView = cell.imageView {
/** 1. use the same UIImage object created by animatedImageWithImages
the image disappear when reuse, and when touching cell, it reappear. */
imageView.image = self._gifImage
/** 2. create different UIImage by using same image array. this still not work */
// imageView.image = UIImage.animatedImageWithImages(self._gifImageArray, duration: NSTimeInterval(self._gifImageArray.count) * 0.1)
/** 3. create different UIImage from data, this seems work
but use a lot of memories. and seems it has performance issue. */
// imageView.image = UIImage.gifImage(self._gifData)
/** 4. use same image array as imageView's animationImages.
this kind works, but have different api than just set image.
and when click on cell, the animation stops. */
// imageView.image = self._gifImageArray[0]
// imageView.animationImages = self._gifImageArray
// imageView.startAnimating()
}
return cell
}
}
Notice in the cellForRowAtIndexPath: method, I tried different way to set imageView.
when I po object in lldb, I notice the animated imageView have a CAKeyframeAnimation. does this makes a tip?
How can I make the gif shown perfectly? Any suggestion would be helpful.
Here is a preview: after scroll, gif disappear, and reappear when touch
After I tried many times, I have finally figured out that I need to change:
imageView.image = self._gifImage
to:
imageView.image = nil // this is the magical!
imageView.image = self._gifImage
just set image to nil before set to new image, and the animation work!! It's just like a magical!
This is definitely apple's bug.
try by replacing imageView with myImageview or other name because cell have default imageView property so when you call cell.imageView then it returns cell's default imageview i think. so change your cell's imageview's variable name. hope this will help :)
maybe image class need SDAnimatedImage instead of UIImage