I have an issue on my swift iOS app and i'm not able to understand which is the real problem. I have a UITabBarController with five UINavigationControllers on each tab. Main content of these controllers are UICollectionView filled dynamically from a WebService with custom UICollectionViewCells (about 30 for each CollectionView) with custom UIImageViews, custom UILabels and custom UIButtons loaded from custom xib files.
When i push few controllers all is working good (CollectionViews become a bit slower but i think it's normal for memory usage) but when a NavigationController contains 6 or 7 controllers and i try to scroll down, my UI is freezed, the app doesn't crash and i don't receive any kind of runtime error. I can still exit using Home button but the app is completely locked.
I'm dequeuing every cell in every collectionView with identifier and i'm using this attributes in every custom cell for performance:
layer.shouldRasterize = true
layer.rasterizationScale = UIScreen().scale
What i tried
Debug views too see if some UIWindow or UIView is covering the controller view
Stop image loading and show blank imageViews
Clear code used in UICollectionView scroll delegate methods
But the issue is always reproducible on 6th or 7th controller, making some stress test. I gave a look to FPS too, but values are always from 40 to 60 that seems good. CPU usage is always very high, 90/100 % but method didReceiveMemoryWarning is never fired.
My goal would be that my app could push many controllers without freezing or become very very slow, but i can't understand what i'm missing.
Thanks to Philip Mills
Issue is that i was calling this code in my custom UICollectionViewCell setup:
func rotateWithDuration(duration: NSTimeInterval, indeterminate: Bool) {
UIView.animateWithDuration(duration, delay: 0.0, options: .CurveLinear, animations: {
self.transform = CGAffineTransformRotate(self.transform, CGFloat(M_PI))
}, completion: { finished -> Void in
if indeterminate {
self.rotateWithDuration(duration, indeterminate: indeterminate)
}
})
}
View rotation animation was running indefinitely in every cell, so the CPU was a bit sweat.
Related
I have a tableview that has expandable cells. When you click on a cell a date picker shows inline. When you click on another, a different kind of picker shows. I have set it up so when you click on any cell, any open cells will close, so as to ensure that only one cell is open at a time.
My problem is: the animations for a) the first cell closing, and b) the second cell opening are overlapping, which doesn't look great at all.
When I click a cell I would like the expanded cell to close, and THEN the second cell to expand, with enough of a delay so the animations don't overlap.
I have looked for a Swift solution online but haven't been able to find an answer. I am fairly new to iOS dev, so don't know much about UIAnimation, etc.
Using Xcode 7.2, Swift.
Any help would be appreciated.
You can try to use the completion for that.
UIView.animateWithDuration(0.5, delay: 0.0, options: UIViewAnimationOptions.CurveLinear, animations: {
//your first animation
}, completion: {(finished:Bool) in
//your seconde animation (or whatever you want to be done when the first one is completed)
})
OR
You can use a CATransaction, see : Wait for Swift animation to complete before executing code
OR
You can try to force them to run on the same thread instead of using parallel threads ?
For example, here is how you can force an action to be done on the main thread :
dispatch_async(dispatch_get_main_queue()) {
//your animation
}
There may also be some other solutions,
good luck
Problem
scrolling to the bottom of table view is slow
the table view contains about 25 items
and there is a bit of complexity in creating each cell
so creating 25 of them at the same time takes about 200 ms(believe me there is no way I can make it any more optimized)
Soloutions I've tried
there are basically two options for scrolling to bottom of table view(And I have searched the entire stack over flow for this)
tableView.scrollRectToVisible OR tableView.setContentOffset
tableView.scrollToRowAtIndexPath
the problem with the first option is, although it is pretty fast and it doesn't recreate every cell in the way from top to bottom of tableView, I need to call a new main queue to get it to work. if I call it in the same queue as the one that called tableView.reloadData(), then there will be a strange lag when scrolling the tableview(logs show that lots of cells are being re created unnecessarily, and I mean lots of them). and let me add that both threads are main thread. but still I must call the dispatch_get_main_queue() to get rid of that awkward lag. however new main thread adds a good delay to the initial load.
here is my code:
func loadData(groupID: String){
self._chats = self._cache[groupID]!
_tableView.reloadData()
dispatch_async(dispatch_get_main_queue()) {
let rect = CGRect(x: 0, y: self._tableView.contentSize.height - self._tableView.bounds.height, width: self._tableView.contentSize.width, height: self._tableView.bounds.height)
self._tableView.scrollRectToVisible(rect, animated: false)
}
}
again if I don't use the dispatch_get_main_queue() the tableView would be supper laggy. and if I do use it, then there will be about 100 ms delay in execution which makes the initial loading slow.
the second option is slow too, cause scrolling to the last indexPath means creating every cell in the way. and that cannot happen in the background and doing it in the main thread means freezing the UI for about 200 ms.
can you suggest any other solution or find something wrong with what I'm doing? I've tried every thing and still tableView isn't fast enough.
You can use Time Profiler one of Xcode Instruments to understand on which of your processes your app stack.
I am building a fairly simple iOS app that, when it boots up, loads a few views and stacks them on top of each other.
However I am experiencing a very long lag between when the app boots and when the views appear. These views are not complicated and take very little time to build.
At first I thought it might have been the image causing the delay, as I grab it via a URL. But after logging some checkpoints, the image load happens fairly quickly.
I did similar checkpoint logging when builidng the UIView and again, it's fast.
So finally, I checked to see if there was a gap between when the call to .addSubview is made and when the views actually appear, and in fact there is.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
let returned = feedMngr.retrieveFeed()
if (returned) {
self.constructFeedStack()
self.view.addSubview(self.firstCardView!)
self.view.insertSubview(self.secondCardView!, belowSubview: self.firstCardView!)
self.view.insertSubview(self.thirdCardView!, belowSubview: self.secondCardView!)
self.view.insertSubview(self.fourthCardView!, belowSubview: self.thirdCardView!)
NSLog("fin")
}
}
After "fin" is logged (roughly 5 seconds after app launch), there is a 5-10 second lag before the views are actually presented.
Anyone know why this may be the case?
You're modifying the view hierarchy off of the main queue ("in the background"). This isn't allowed. Your UIKit calls all need to be on the main queue.
In our iOS app, we have a screen with a UINavigationController, three UIBarButtonItem in the rightBarButtonItems, a UIView content view with a UITableView subview.
Every time the app gets to the foreground, VoiceOver places the initial focus on the leftmost UIBarButtonItem. We would like to have the focus on either the topmost cell in the UITableView (initial start) or the cell that the user actively selected before the app went to the background.
We have tried to call UIAccessibilityPostNotification() but this only works if we add a delay and that seems very fragile and will be confusing for the user as focus will start on the UIBarButtonItem and then jump.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(0.75*Double(NSEC_PER_SEC))), dispatch_get_main_queue()) { () -> Void in
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, self.view)
}
This might work.
As far as I know uiaccessibilitypostnotification works well with the delay, even though it is fragile, you can call the method with 0.5 seconds delay in the view did appear method!!!!
There is also another way you can try. Once the view is loaded, disable the accessibility for the bar buttons, and set the accessibility only for the tableview!!! You need to change the accessibilityElements array and the first element in the array should be the table view, and set isAccessibilityElemnt to NO for the other elements to turn off the accessibility!!!!
On OS X, one can hold the shift key to cause animation to slow down. Is there some way to apply this to iOS via a remote debugger or in Instruments?
(Alternatively, I could record in QuickTime and replay frame by frame, but I would rather not have to do that.)
EDIT: I'm looking for a way to do this on the device.
You can slow down animations in your iOS Simulator by clicking Debug-->Toogle Slow Animations in frontmost app.
Temporarily set layer.speed on the view you are animating to a value less than one. For example in my UIViewController subclass, I have a UIView subclass which is animating some changes to it's subviews. If I want to slow down those animations (and only those, not other transitions):
class MyViewController: UIViewController {
private let myView = MyView()
override func viewDidLoad() {
super.viewDidLoad()
// slow down animation speed for only this view
myView.layer.speed = 0.1
}
}
If you want to slow down all animations in your app, set window?.layer.speed, for example in your AppDelegate.
Don't forget to remove this code once you're done perfecting your animation!
Select the simulator and use ⌘T
You will get an output in the console telling you if it's been turned on or off.