I got a problem with UIImage. Each time I call the function below, the memory increases by 5 MB.
I have 200 images, I'm scared it will crash on my iPad 2 (512 MB) so I did added pagination to my app - each page contains 20 images.
And it is still crashing. How can I reduce the amount memory on each page?
func loadImage(index:Int){
if self.imgPaths.count == 0 {
println("Has not data")
actInd.stopAnimating()
return
}
var imgURL: NSURL = NSURL(string: self.imgPaths[index].stringByTrimmingCharactersInSet(.whitespaceAndNewlineCharacterSet()))!
let width:CGFloat = self.view.bounds.width
let height:CGFloat = self.view.bounds.height
var view:UIView = UIView(frame: CGRectMake(0, 0, width, height));
if let imgObj = self.dicData[index] {
}
else
{
println("imgURL \(imgURL)")
let request: NSURLRequest = NSURLRequest(URL: imgURL)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler: {(response: NSURLResponse!,data: NSData!,error: NSError!) -> Void in
if error == nil {
let imgItem = UIImage(data: data)!
var te :Float = self.imgPaths.count > 0 ? Float(index + 1) / Float(self.imgPaths.count) : 1
self.progressView.setProgress(te, animated: true)
if let imgObj = self.dicData[index] {
if index < self.imgPaths.count - 1
{
var nextIndex:Int = index + 1
self.loadImage(nextIndex)
}
if(index == self.imgPaths.count - 1)
{
if self.currentImageIndex > 0
{
self.isAddedFirstImg = true
}
if !self.isAddedFirstImg
{
self.scrollViews[0].zoomScale = self.zoomScales[0]
self.view.insertSubview(self.scrollViews[0], belowSubview: self.tabBar.viewWithTag(77)!)
self.isAddedFirstImg = true
}
self.actInd.stopAnimating()
println("loaded image")
}
}
else
{
self.dicData[index] = UIImageView(image: imgItem)
self.dicData[index]?.frame = CGRect(origin: CGPointMake(0.0, 0.0), size:imgItem.size)
// 2
self.scrollViews[index].addSubview(self.dicData[index]!)
self.scrollViews[index].contentSize = imgItem.size
// 3
var doubleTapRecognizer = UITapGestureRecognizer(target: self, action: "scrollViewDoubleTapped:")
doubleTapRecognizer.numberOfTapsRequired = 2
doubleTapRecognizer.numberOfTouchesRequired = 1
self.scrollViews[index].addGestureRecognizer(doubleTapRecognizer)
var singleTapRecognizer = UITapGestureRecognizer(target: self, action: "scrollViewSingleTapped:")
singleTapRecognizer.numberOfTapsRequired = 1
singleTapRecognizer.numberOfTouchesRequired = 1
self.scrollViews[index].addGestureRecognizer(singleTapRecognizer)
var swipeRight = UISwipeGestureRecognizer(target: self, action: "respondToSwipeGesture:")
swipeRight.direction = UISwipeGestureRecognizerDirection.Right
self.scrollViews[index].addGestureRecognizer(swipeRight)
var swipeLeft = UISwipeGestureRecognizer(target: self, action: "respondToSwipeGesture:")
swipeLeft.direction = UISwipeGestureRecognizerDirection.Left
self.scrollViews[index].addGestureRecognizer(swipeLeft)
// 4
var scrollViewFrame = self.scrollViews[index].frame
var scaleWidth = scrollViewFrame.size.width / self.scrollViews[index].contentSize.width
var scaleHeight = scrollViewFrame.size.height / self.scrollViews[index].contentSize.height
var minScale = min(scaleWidth, scaleHeight)
self.zoomScales[index] = minScale
self.scrollViews[index].minimumZoomScale = minScale
// 5
self.scrollViews[index].maximumZoomScale = 1.0
self.scrollViews[index].delegate = self
// 6
self.centerScrollViewContents(index)
dispatch_async(dispatch_get_main_queue(), {
println("downloaded image index: \(index) CH.\(self.chapterID)")
if(index == 0)
{
self.scrollViews[0].zoomScale = self.zoomScales[0]
self.view.insertSubview(self.scrollViews[0], belowSubview: self.tabBar.viewWithTag(77)!)
self.actInd.stopAnimating()
}
if index < self.imgPaths.count - 1 && !self.stopDownload
{
var nextIndex:Int = index + 1
self.loadImage(nextIndex)
}
if(index == self.imgPaths.count - 1)
{
if self.currentImageIndex > 0
{
self.isAddedFirstImg = true
}
if !self.isAddedFirstImg
{
self.scrollViews[0].zoomScale = self.zoomScales[0]
self.view.insertSubview(self.scrollViews[0], belowSubview: self.tabBar.viewWithTag(77)!)
self.isAddedFirstImg = true
}
self.actInd.stopAnimating()
println("loaded image")
}
})
}
}
else {
println("Error: \(error.localizedDescription)")
}
})
}
}
Your intuition here (not trying to load 200 high resolution images at the same time) is right.
There are two issues here:
If the image dimensions greatly exceed the dimensions of the image views in which you're showing them (or more accurately, the dimensions of the image view times the device's scale), then you should resize the image to the size appropriate for the current image view. Loading a large image into a small image view still requires a great deal of memory. So, resize the image if necessary.
You should only hold in memory those images that are visible at any given time. Those that scroll out of view should be removed from memory. The images not currently visible should be cached to some persistent storage so that you can free up memory while being able to re-retrieve the image quickly without having to go back to the web service for the image.
It would appear that you're manually adding image view objects to a scroll view. To do this properly with a scroll view involves complicated UIScrollViewDelegate logic that determines which image views are visible and removes the ones that outside the visible portion of the scroll view. It's much easier to use UITableView or UICollectionView or UIPageViewController, which give you mechanisms to manager/reuse image views as they scroll in and out of view. Deciding which of these is appropriate depends a little upon the desired UI. But hopefully this illustrates the idea: Don't try to load all images in the scroll view at the same time. And rather than manually paging yourself, consider using one of the iOS built-in controls which greatly simplifies this process.
As an aside, you might also consider using a image framework like the UIImageView category provided by SDWebImage or AFNetworking. These allow a very simple mechanism for asynchronously retrieving images from web service. You'll still want to use one of the above mechanisms for managing the image views, but either of these two frameworks will simplify your networking code (lazy loading, asynchronous image retrieval, caching of images, concurrent image retrieval which will greatly improve performances, etc.).
You need to use Haneke instead NSdata. The problem is your app download synchronously, not asynchronously.
Haneke
imageView.hnk_setImageFromURL(url)
Reference:
http://blog.revivalx.com/2015/02/23/uitableview-tutorial-in-swift-using-alamofire-haneke-and-swiftyjson/
https://github.com/Haneke/HanekeSwift
Related
I am looking to improve my memory usage on my app and noticed that using AdMob is adversely affecting my application.
Specifically, with this code:
override func viewDidLoad() {
super.viewDidLoad()
multipleAdsOptions.numberOfAds = 5
let imageAdOptions = GADNativeAdImageAdLoaderOptions()
adLoader = GADAdLoader(adUnitID: "ca-app-pub-xxxxxxxx", rootViewController: self, adTypes: [GADAdLoaderAdType.native], options: [multipleAdsOptions, imageAdOptions])
adLoader.delegate = self
GADMobileAds.sharedInstance().requestConfiguration.testDeviceIdentifiers = [ kGADSimulatorID.description() ]
DispatchQueue.main.async {
self.adLoader.load(GADRequest())
}
..
..
}
I noticed that each time I trigger a viewDidLoad in my application for this code, it increases the memory used by about 10-15mb. If I quickly tap in and out of this screen (which is actually a common action) I can get the MB usage up as high as I want until I stop. I noticed that this line of code is of course the culprit:
self.adLoader.load(GADRequest())
If I comment that out, my app stays at a clean 50mb and even appears to run much better overall.
Am I using AdMob wrong here? should I be loading the ads once and never calling load again in my viewdidload? This is a highly trafficked screen and my test users are reporting app sluggishness until they restart.
This is my main screen (root view controller) and it contains a feed of products and I also have an AdvertisementFeedCell where I load an ad every 5 items in this feed if that makes sense.
class AdvertisementFeedCell: UITableViewCell {
#IBOutlet var adMedia: GADNativeAdView!
#IBOutlet var adHeadline: UILabel!
#IBOutlet var unifiedNativeAdView: GADNativeAdView!
override func awakeFromNib() {
super.awakeFromNib()
}
}
When I populate my table, I insert a new item every 5 spots for an Ad:
let count = self.feedItems.count / 5
//Insert an ad every 5
if (count > 0)
{
for i in 1...count
{
let adFeedItem = FeedItem()
adFeedItem.isAdvertisement = true
self.feedItems.insert(adFeedItem, at: i * 5)
}
}
self.reloadTable()
And when my tableview populates with data, I check if the item is of typeadvertisement
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let feedItem = feedItems[indexPath.row]
if (feedItem.isAdvertisement)
{
let cell:AdvertisementFeedCell = tableView.dequeueReusableCell(withIdentifier: "adCell")! as! AdvertisementFeedCell
if (ads.count > 0 && indexPath.row > 1)
{
let nativeAd = ads[(indexPath.row / 5) - 1]
cell.unifiedNativeAdView.nativeAd = nativeAd
cell.adHeadline.text = nativeAd.headline!
//If there is an icon, use that
if (nativeAd.icon != nil)
{
(cell.unifiedNativeAdView.iconView as? UIImageView)?.image = nativeAd.icon?.image
cell.unifiedNativeAdView.mediaView?.isHidden = true
cell.unifiedNativeAdView.iconView?.isHidden = false
}
else {
//Otherwise, Add Media and hide icon
cell.unifiedNativeAdView.mediaView?.contentMode = UIViewContentMode.scaleToFill
cell.unifiedNativeAdView.mediaView?.mediaContent = nativeAd.mediaContent
cell.unifiedNativeAdView.mediaView?.isHidden = false
cell.unifiedNativeAdView.iconView?.isHidden = true
}
cell.frame = CGRect(x: 0, y: 0, width: 375, height: 108 )
//Add body text if available
(cell.unifiedNativeAdView.bodyView as? UILabel)?.text = nativeAd.body
cell.unifiedNativeAdView.bodyView?.isHidden = nativeAd.body == nil
cell.clipsToBounds = true
cell.unifiedNativeAdView.clipsToBounds = true
}
return cell
}
Thank you!
While debugging the code in this question UILabel doesn't update after button clickl I noticed that the simulated memory usage kept growing, even when I was in the debugger and not viewing the app.
I modified the addButtonsAndLabels() function to this to prevent memory loss, is this a bad practice?
func addButtonAndLabels() -> Void {
// If the width of the screen hasn't been used as a base for the size of the sub-views then
// this function is not ready to generate the sub-views.
if (selfWidth < 1.0) {
return;
}
var viewElementVerticalLocation: CGFloat = startingVerticalLocation;
// To prevent memory leaks only create the UIView object if it hasn't already been created
if (self.getGPSLongitudeAndLatitudeWithTimeStamp == nil) {
self.getGPSLongitudeAndLatitudeWithTimeStamp = makeAButton(yButtonStart: viewElementVerticalLocation, buttonTitle: "Get GPS Location with TimeStamp", underSubview: nil)
self.getGPSLongitudeAndLatitudeWithTimeStamp?.addTarget(self, action: #selector(setLabelWithGPSLatitudeAndLongitudeWithTimeStampData), for: .touchUpInside)
viewElementVerticalLocation += buttonVerticalSeparation
}
// ... more of the above ...
}
Is this code interfering with ARC? Is this code unnecessary?
I also noticed that viewDidLoad() was called before init(), since I had breakpoints in both functions.
If this is a duplicate please point me in the correct direction.
EDIT
import UIKit
class ViewController: UIViewController {
var getGPSLongitudeAndLatitudeWithTimeStamp : UIButton?
var getBatteryLevelAndState : UIButton?
var getNextorkImplementation : UIButton?
var displayButtonAction : UILabel?
var displayDataModel : PCI7DataModelLibrary?
// The following variables are used in multiple functions. They are constant during the display of the super view
// and control the size of the subviews. They should change when the orientation changes
var selfWidth : CGFloat = 0.0
var buttonHeight : CGFloat = 0.0
var viewElementWidth : CGFloat = 0.0
var buttonYCenterOffset : CGFloat = 0.0 // The y center should be half the value of the height
var buttonXCenter : CGFloat = 0.0 // Center the button title relative to the width of the button and the width of the super view
var buttonXInit : CGFloat = 0.0
var buttonVerticalSeparation : CGFloat = 0.0
var startingVerticalLocation : CGFloat = 0.0
var displayLabelHeight: CGFloat = 75.0
// TODO: This function should be altered so that all values are calculated on screen height and screen width,
// this will allow for changes in orientation.
func initFramingValuesOfMyDisplay() {
selfWidth = self.view.bounds.size.width
buttonHeight = 20.0 // This should be programmable in relative to self.view.bounds.size.height
viewElementWidth = 0.8 * selfWidth;
buttonXCenter = selfWidth / 2.0; // Center the button title relative to the width of the button and the width of the super view
buttonXInit = selfWidth * 0.1; // 10 percent margin on the left leaves a 10% margin on the right as well
buttonYCenterOffset = buttonHeight / 2.0; // The y center should be half the value of the height
buttonVerticalSeparation = buttonHeight + buttonYCenterOffset;
startingVerticalLocation = 430.0; // 430 was chosen based on experimentation in the simulator
}
// This function is called when the getGPSLongitudeAndLatitudeWithTimeStamp button is receives the touchUpInside event.
func setLabelWithGPSLatitudeAndLongitudeWithTimeStampData()
{
var actionString : String = "Testing Label Text"
if (self.displayDataModel != nil) {
actionString = (self.displayDataModel?.provideGPSLocationData())!
}
else {
actionString = "GPS Button Action Failure: Data Model not created"
}
DispatchQueue.main.async {
self.displayButtonAction?.text = nil
self.displayButtonAction?.text = actionString
}
}
// This function is called when the getBatteryLevelAndState button is receives the touchUpInside event.
func setLabelWithBatteryLevelAndState() {
var actionString : String = "Get Battery Level and State";
if (self.displayDataModel != nil) {
actionString = (self.displayDataModel?.provideBatteryLevelAndState())!
}
else {
actionString = "Battery Button Action Failure: Data Model not created"
}
DispatchQueue.main.async {
self.displayButtonAction?.text = nil
self.displayButtonAction?.text = actionString
}
}
// This function is called when the getNextorkImplementation button is receives the touchUpInside event.
func setLabelActionNetwork() {
var actionString :String = "Fake Button set to American Express Stock Price"
if (self.displayDataModel != nil) {
actionString = (self.displayDataModel?.provideNetworkAccessData())!
}
else {
actionString = "Network Button Action Failure: Data Model not created"
}
DispatchQueue.main.async {
self.displayButtonAction?.text = nil
self.displayButtonAction?.text = actionString
}
}
func makeAButton(yButtonStart : CGFloat, buttonTitle: String, underSubview: UIButton?) -> UIButton
{
let thisButton = UIButton.init(type: .system)
thisButton.frame = CGRect(x: buttonXInit, y: yButtonStart, width: viewElementWidth, height: buttonHeight)
thisButton.setTitle(buttonTitle, for:UIControlState.normal)
thisButton.backgroundColor = UIColor.yellow
thisButton.setTitleColor(UIColor.black, for: UIControlState.normal)
if ((underSubview) == nil) {
self.view.addSubview(thisButton)
}
else {
self.view.insertSubview(thisButton, belowSubview:underSubview!)
}
return thisButton;
}
func makeALabel(yLabelStart : CGFloat, height: CGFloat, underSubview: UIButton?) -> UILabel
{
let thisLabel = UILabel.init()
thisLabel.frame = CGRect(x: buttonXInit, y: yLabelStart, width: viewElementWidth, height: height)
thisLabel.font = thisLabel.font.withSize(12) // Reduce the size of the text so that more output fits on a single line
thisLabel.lineBreakMode = .byWordWrapping;
thisLabel.numberOfLines = 0; // Allow the label to grow as necessary
thisLabel.textAlignment = NSTextAlignment.center;
thisLabel.textColor = UIColor.black;
if ((underSubview) == nil) {
self.view.addSubview(thisLabel)
}
else {
self.view.insertSubview(thisLabel, belowSubview:underSubview!)
}
return thisLabel;
}
override func viewDidLoad() {
super.viewDidLoad()
// rather than assume a particular background color, set the background color so that everything can be seen.
self.view.backgroundColor = UIColor.white
initFramingValuesOfMyDisplay()
if (self.displayDataModel == nil) {
self.displayDataModel = PCI7DataModelLibrary.init()
}
addButtonAndLabels()
}
func addButtonAndLabels() -> Void {
// If the width of the screen hasn't been used as a base for the size of the sub-views then
// this function is not ready to generate the sub-views.
if (selfWidth < 1.0) {
return;
}
var viewElementVerticalLocation: CGFloat = startingVerticalLocation;
// To prevent memory leaks only create the UIView object if it hasn't already been created
if (self.getGPSLongitudeAndLatitudeWithTimeStamp == nil) {
self.getGPSLongitudeAndLatitudeWithTimeStamp = makeAButton(yButtonStart: viewElementVerticalLocation, buttonTitle: "Get GPS Location with TimeStamp", underSubview: nil)
self.getGPSLongitudeAndLatitudeWithTimeStamp?.addTarget(self, action: #selector(setLabelWithGPSLatitudeAndLongitudeWithTimeStampData), for: .touchUpInside)
viewElementVerticalLocation += buttonVerticalSeparation
}
if (self.getGPSLongitudeAndLatitudeWithTimeStamp == nil) {
self.getBatteryLevelAndState = makeAButton(yButtonStart: viewElementVerticalLocation, buttonTitle: "Get Battery Level and State", underSubview: getGPSLongitudeAndLatitudeWithTimeStamp)
self.getBatteryLevelAndState?.addTarget(self, action: #selector(setLabelWithBatteryLevelAndState), for: .touchUpInside)
viewElementVerticalLocation += buttonVerticalSeparation
}
if (self.getNextorkImplementation == nil) {
self.getNextorkImplementation = makeAButton(yButtonStart: viewElementVerticalLocation, buttonTitle: "Get American Express Stock Price", underSubview: getBatteryLevelAndState)
self.getNextorkImplementation?.addTarget(self, action: #selector(setLabelActionNetwork), for: .touchUpInside)
viewElementVerticalLocation += buttonVerticalSeparation
}
if (self.displayButtonAction == nil) {
self.displayButtonAction = makeALabel(yLabelStart: viewElementVerticalLocation, height: displayLabelHeight, underSubview: getNextorkImplementation)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
required init(coder aDecoder: NSCoder) {
super.init(nibName: nil, bundle: nil)
if (self.displayDataModel == nil) {
self.displayDataModel = PCI7DataModelLibrary.init()
}
initFramingValuesOfMyDisplay()
}
}
It's a little difficult to answer this question because I fear you have a lot of misunderstandings about how Swift memory management works and how iOS view controller life cycles work. They're not surprising misunderstandings; it's just hard to know where to start here. I may cover things you're already aware of.
First, start with studying the Automatic Reference Counting section of the Swift manual. The key to understanding ARC is that it is based on strong references. If two things have strong references to each other, that's a retain loop and until it's broken, neither object will be deallocated.
It isn't really meaningful to ask "is Swift by default autorelease." Autorelease is a slightly more advanced concept that you probably don't have to understand for basic Swift. (I've worked with it so long that I hope I'm not being overly dismissive of whether people need to understand it, but I think you can avoid thinking about it for now.) Autorelease has to do with objects that you want to exist for the rest of the current event loop, but would otherwise have a zero retain count. While it still exists in ARC, it's mostly for manual retain counting, and you rarely need to think too much about it in Swift, particularly as a beginner.
I also noticed that viewDidLoad() was called before init(), since I had breakpoints in both functions.
It's not possible for viewDidLoad to be called before init on a given object. Your problem, almost certainly, is that there are more than one of this object, and that's surprising you. Generally if you print(self) (or p self in the debugger) it will show you the address of the object. If it has a different address, it's a different object. You may want to implement deinit so you can put a breakpoint there as well and explore the life cycle of the view controller.
I assume somewhere there's an addSubview call on this button. What's almost certainly happening is that you're calling addButtonAndLabels multiple times (perhaps because you are confused about view lifecycle), and each call adds the button to the view. So you get more and more buttons in the view, and each takes memory. You need to make sure you only add things once if you only want one. You can see this problem with p view.subviews in the debugger (or print(view.subviews)). You'll probably see a ton of buttons. This isn't a leak. These are just objects you're creating that you didn't mean to create.
I recently switched from Objective C to Swift, so I am not familiar with finding mistakes in my own algorithm. I would like to understand what am I missing here.
I've got ViewController with ScrollView added. Everything compiles and runs, no SIGARBT's or so, but It won't display the desired images. Every IBOutlet is connected propely in its place.
Below is the function for all of the UI Elements:
func UIUpdate() {
guard let imageContent = content as? ImageContent,
pages = imageContent.pages?.array as? [Image] else {
return
}
self.pages = pages
self.mainTitleLabel.text = imageContent.title
self.pageControl.numberOfPages = pages.count
let imageWidth = UIScreen.mainScreen().bounds.size.width// - 20
self.scrollView.frame.size.width = imageWidth
self.scrollView.frame.size.height = imageWidth / 1.883
self.scrollView.contentSize = CGSizeMake(imageWidth *
CGFloat(pages.count), self.scrollView.frame.size.height)
for (index, page) in pages.enumerate() {
autoreleasepool {
guard let imageData = page.image, let image = UIImage(data: imageData) else { return }
let imageView = UIImageView(image: image)
imageView.contentMode = .ScaleAspectFit
imageView.frame.size = CGSizeMake(self.scrollView.frame.size.width, self.scrollView.frame.size.height)
self.scrollView.addSubview(imageView)
self.addConstraints(toImageView: imageView, atIndex: index, lastIndex: pages.count - 1)
}
}
}
Image class is where images and titles are parsed. So this class is perfectly fine, because I have been using it in another project. This method is the only one called for getting those images. There are methods though, but I think they've got nothing to do with my problem.
Could You guys show me the mistake! Any help would be appreciated!
So I have seen a lot of posts about reordering cells that pertain to using "edit mode", but none for the problem I have. (Excuse me if I am wrong).
I am building a ranking app, and looking for a way to use a long gesture recognizer to reorder the cells in my UITableView. Essentially a user will be able to reorder and "Rank" the cells full of strings in a group with their friends.
I would go the standard route of using an "edit" bar button item in the nav bar, but I am using the top right of the nav bar for adding new strings to the tableview already. (The following image depicts what I mean).
So far, I have added `
var lpgr = UILongPressGestureRecognizer(target: self, action: "longPressDetected:")
lpgr.minimumPressDuration = 1.0;
tableView.addGestureRecognizer(lpgr)`
to my viewDidLoad method, and started creating the following function:
func longPressDetected(sender: AnyObject) {
var longPress:UILongPressGestureRecognizer = sender as UILongPressGestureRecognizer
var state:UIGestureRecognizerState = longPress.state
let location:CGPoint = longPress.locationInView(self.tableView) as CGPoint
var indexPath = self.tableView.indexPathForRowAtPoint(location)?
var snapshot:UIView!
var sourceIndexPath:NSIndexPath!
}
All of the resources I have scowered for on the internet end up showing me a HUGE, LONG list of additives to that function in order to get the desired result, but those examples involve core data. It seems to me that there must be a far easier way to simply reorder tableview cells with a long press?
Dave's answer is great.
Here is the swift 4 version of this tutorial:
WayPointCell is your CustomUITableViewCell and wayPoints is the dataSource array for the UITableView
First, put this in your viewDidLoad, like Alfi mentionend:
override func viewDidLoad() {
super.viewDidLoad()
let longpress = UILongPressGestureRecognizer(target: self, action: #selector(longPressGestureRecognized(gestureRecognizer:)))
self.tableView.addGestureRecognizer(longpress)
}
Then implement the method:
func longPressGestureRecognized(gestureRecognizer: UIGestureRecognizer) {
let longpress = gestureRecognizer as! UILongPressGestureRecognizer
let state = longpress.state
let locationInView = longpress.location(in: self.tableView)
var indexPath = self.tableView.indexPathForRow(at: locationInView)
switch state {
case .began:
if indexPath != nil {
Path.initialIndexPath = indexPath
let cell = self.tableView.cellForRow(at: indexPath!) as! WayPointCell
My.cellSnapShot = snapshopOfCell(inputView: cell)
var center = cell.center
My.cellSnapShot?.center = center
My.cellSnapShot?.alpha = 0.0
self.tableView.addSubview(My.cellSnapShot!)
UIView.animate(withDuration: 0.25, animations: {
center.y = locationInView.y
My.cellSnapShot?.center = center
My.cellSnapShot?.transform = CGAffineTransform(scaleX: 1.05, y: 1.05)
My.cellSnapShot?.alpha = 0.98
cell.alpha = 0.0
}, completion: { (finished) -> Void in
if finished {
cell.isHidden = true
}
})
}
case .changed:
var center = My.cellSnapShot?.center
center?.y = locationInView.y
My.cellSnapShot?.center = center!
if ((indexPath != nil) && (indexPath != Path.initialIndexPath)) {
self.wayPoints.swapAt((indexPath?.row)!, (Path.initialIndexPath?.row)!)
//swap(&self.wayPoints[(indexPath?.row)!], &self.wayPoints[(Path.initialIndexPath?.row)!])
self.tableView.moveRow(at: Path.initialIndexPath!, to: indexPath!)
Path.initialIndexPath = indexPath
}
default:
let cell = self.tableView.cellForRow(at: Path.initialIndexPath!) as! WayPointCell
cell.isHidden = false
cell.alpha = 0.0
UIView.animate(withDuration: 0.25, animations: {
My.cellSnapShot?.center = cell.center
My.cellSnapShot?.transform = .identity
My.cellSnapShot?.alpha = 0.0
cell.alpha = 1.0
}, completion: { (finished) -> Void in
if finished {
Path.initialIndexPath = nil
My.cellSnapShot?.removeFromSuperview()
My.cellSnapShot = nil
}
})
}
}
func snapshopOfCell(inputView: UIView) -> UIView {
UIGraphicsBeginImageContextWithOptions(inputView.bounds.size, false, 0.0)
inputView.layer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
let cellSnapshot : UIView = UIImageView(image: image)
cellSnapshot.layer.masksToBounds = false
cellSnapshot.layer.cornerRadius = 0.0
cellSnapshot.layer.shadowOffset = CGSize(width: -5.0, height: 0.0)
cellSnapshot.layer.shadowRadius = 5.0
cellSnapshot.layer.shadowOpacity = 0.4
return cellSnapshot
}
struct My {
static var cellSnapShot: UIView? = nil
}
struct Path {
static var initialIndexPath: IndexPath? = nil
}
Give this tutorial a shot, you'll likely be up and running within 20 minutes:
Great Swift Drag & Drop tutorial
It's easy. I've only been developing for 3 months and I was able to implement this. I also tried several others and this was the one I could understand.
It's written in Swift and it's practically cut and paste. You add the longPress code to your viewDidLoad and then paste the function into the 'body' of your class. The tutorial will guide you but there's not much more to it.
Quick explanation of the code: This method uses a switch statement to detect whether the longPress just began, changed, or is in default. Different code runs for each case. It takes a snapshot/picture of your long-pressed cell, hides your cell, and moves the snapshot around. When you finished, it unhides your cell and removes the snapshot from the view.
Warning: My one word of caution is that although this drag/drop looks great and works close to perfectly, there does seem to be an issue where it crashes upon dragging the cell below the lowest/bottom cell.
Drag & Drop Crash Problem
Since iOS 11 this can be achieved by implementing the built in UITableView drag and drop delegates.
You will find a detailed description of how to implement them in this answer to a similar question:
https://stackoverflow.com/a/57225766/10060753
I'm trying to animate images in particular time- duration. It is working fine in Objective C. However, it is not working for Swift, where is my mistake?
The code for Objective-C is -
-(void)viewDidLoad
{
[super viewDidLoad];
NSMutableArray *imgListArray=[NSMutableArray array];
for (int i=0; i<=11; i++)
{
NSString *strImageName=[NSString stringWithFormat:#"c%d.png", i];
NSLog(#"%#",strImageName);
UIImage *image=[UIImage imageNamed:strImageName];
[imgListArray addObject:image];
}
self.imgView.animationImages = imgListArray;
self.imgView.animationDuration =1.0f;
[self.imgView startAnimating];
// Do any additional setup after loading the view, typically from a nib
}
The Code for swift is-
override func viewDidLoad()
{
super.viewDidLoad()
var imgListArray :NSMutableArray = []
for countValue in 1...11
{
var strImageName : String = "c\(countValue).png"
var image = UIImage(named:strImageName) // suggested by Anil
imgListArray.addObject(image)
}
// Swift code HERE for Objective c
}
[UIImage imageNamed (strImageName)]
This not swift code. In swift it would be
UIImage(named:strImageName)
Modified code:
var imgListArray :NSMutableArray = []
for countValue in 1...11
{
var strImageName : String = "c\(countValue).png"
var image = UIImage(named:strImageName)
imgListArray .addObject(image)
}
self.imageView.animationImages = imgListArray;
self.imageView.animationDuration = 1.0
self.imageView.startAnimating()
for Swift 2, use [UIImage] instead.
var images: [UIImage] = []
for i in 1...2 {
images.append(UIImage(named: "c\(i)")!)
}
myImageView.animationImages = images
myImageView.animationDuration = 1.0
myImageView.startAnimating()
In swift you can go one step further and have a simple line to do this:
let loadingImages = (1...11).map { UIImage(named: "c\($0)")! }
Then you can do the rest and inject this into an imageView
self.imageView.animationImages = loadingImages
self.imageView.animationDuration = 1.0
self.imageView.startAnimating()
In swift 3 - Create Array of images and just animate that.
func animate_images()
{
let myimgArr = ["1.jpg","2.jpg","3.jpg"]
var images = [UIImage]()
for i in 0..<myimgArr.count
{
images.append(UIImage(named: myimgArr[i])!)
}
imgView_ref.animationImages = images
imgView_ref.animationDuration = 0.04
imgView_ref.animationRepeatCount = 2
imgView_ref.startAnimating()
}
And for stop animation just write
imgView_ref.stopAnimating()
Swift 3+
UIImageViews can animate images in two different ways (the other answers already cover the first way in deep):
Create an [UIImage] array with the images to animate
Set the animationImages property of the view with this array
Set the animationDuration property
Start the animation
Encapsulate the animation in an UIImage using animatedImage(with:duration:)
Set the normal image property of the view
The following code uses #imageLiterals:
let images = [img1, img2, img3] // use of #imageLiterals here
let animation = UIImage.animatedImage(with: images, duration: 1)
self.myImageView.image = animation
Pro:
If you have to change the image of an UIImageView a lot and maybe one of those images should be animated, then you don't have to start/stop the animation every time anymore neither you need to set the animatedImages property back to nil to display the image stored in the image property of the image view.
Con:
You can't start/stop the animation, if it's encapsulated in a single UIImage instead of an array.
More on #imageLiterals:
If you want to use an image literal, either type imageLiteral or just type the name of an image from your assets folder and Xcode's code completion will suggest it.
Further reading:
Another good blog post about using #imageLiterals and #colorLiterals with animations to follow.
another approach if you want to animate an array of images:
var counter = 1
var timer = NSTimer()
#IBOutlet weak var someImg: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
timer = NSTimer.scheduledTimerWithTimeInterval(0.3, target: self, selector: Selector("doSomeAnimation"), userInfo: nil, repeats: true)
}
func doSomeAnimation() {
//I have four pngs in my project, which are named frame1.png ... and so on
if counter == 4 {
counter = 1
}else {
counter++
}
someImg.image = UIImage(named: "frame\(counter).png")
}
Hope it helps!!
For anyone running multiple animations, depending on a variable, try the below.
For example, you could run an animation sending 0 or 1 (maybe based on if your app knows its daytime or nighttime):
func runAnimation (imgView: UIImageView, arrayPos: Int) {
let timingArray = [7.0,10.0] //Durations of each
let frameArray = [43,45] //Frames for each
var imgArray: Array<UIImage> = [] //Setup empty array
for i in 1 ... frameArray[arrayPos] {
//Fill empty array with images!!
let imageToAdd = UIImage(named: "animationNum_\(arrayPos)-frameNum_\(i)")
imgArray.append(imageToAdd!)
}
imgView.animationImages = imgArray
imgView.animationDuration = timingArray[arrayPos]
imgView.animationRepeatCount = 2
imgView.startAnimating()
}