I would like to show my scrollview all the time. I have looked around for some answers and i found this which i implemented:
override func viewDidLoad() {
super.viewDidLoad()
_ = NSTimer.scheduledTimerWithTimeInterval(0.001, target: self, selector: #selector(flashIndicator), userInfo: nil, repeats: true)
and the function:
func flashIndicator(){
informationView.flashScrollIndicators()
}
To make sure it is getting called i put a simple print in the function and its getting called many times...
But the scroll is not being shown? Just when im actually scrolling. Anybody knows why?
To the best of my knowledge, Apple frowns upon scroll indicators been constantly shown. Thus, I believe that have deliberately made it a private API. So in short, it may be possible, but I don't think the app would be allowed into the app store.
Related
I have UITabBarController and in one of the UIViewController there I scroll UICollectionView each 5 second using Timer. Here is short code how I do it:
override func viewDidLoad() {
super.viewDidLoad()
configureTimer()
}
private func configureTimer() {
slideTimer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(scrollCollectionView), userInfo: nil, repeats: true)
}
#objc func scrollCollectionView() {
collectionView.scrollToItem(at: someIndexPath, at: .centeredHorizontally, animated: true)
}
It perfectly works. But I think it has a big issue. Of course, I can open another screen from this UIViewController (for example, I can tap to another tab or push another UIViewController). It means, my UIViewController's view, containing UICollectionView, disappears. In another words, viewDidDissapear will be called. But my timer still exists and I am having strong reference to it, possibly, there is retain cycle. It keeps working and each 5 second scrollCollectionView method is called even my view dissapeared. I don't know how, but iOS somehow handles it. In other words, it can modify view even it is not visible. How is that possible and is it good practice? Of course, I can invalidate my timer in viewDidDissapear and start it in viewDidAppear. But I don't want to loose my timer value and don't want to start it from zero again. Or may be it is ok to invalidate my timer in deinit?
My question covers pretty common situation. For instance, if I make network request and open another UIViewController. After that request finished, I should modify UI, but now am on another screen. Is it ok to allow iOS to modify UI even it is not visible?
A couple of thoughts:
If the timer is updating the UI at some interval, you definitely should start it in viewDidAppear and stop it in viewDidDisappear. There’s no point in wasting resources updating a view that is not visible.
By doing this, you can solve your strong reference cycle, too.
In terms of “losing” your timer value and starting at zero, we generally would just save the time you’re counting from or to and calculate the necessary value when restarting the timer later.
We do this, anyway, because you really shouldn’t be using timers to increment values because you’re technically not assured that they’ll be called with the frequency you expect.
All of that said, I don’t know what timer “value” you’re worried about losing in this example.
But definitely don’t waste time updating a UI that is no longer visible. It’s not scalable and blurs the distinction between the model (what you’re counting to or from) from the UI (the update that happens every five seconds).
I was implementing new feature with share button on my app. Notice the activityViewController appeared blank. at first i thought the item i gave to share might be null, but when i tried to share a simple string, it still shows up like this, i revisited my old working code, and they are all acting like this. Even something as simple as this:
let activityViewController = UIActivityViewController(activityItems: ["test"], applicationActivities: nil)
activityViewController.popoverPresentationController?.sourceView = self.view
DispatchQueue.main.async {
self.present(activityViewController, animated: true, completion: nil)
}
this is what i got:
Anybody have any idea what is causing this and how to fix this?
EDIT: Tested Using Actual Device, Causing problems, in my released APP, the feedback is that its not showing blank, it is showing with options, just not inter-actable, can't be clicked and can't dismiss it, the app will just "hang" and then the user will have to kill the app to reuse it. After testing, i'm still not sure what is causing the blank, i've disabled all my UIViewController extension,(many of them is not automated and need functions to call anyway so i don't think they are the problem), and when i put a debugPrint in the completion block of the present function, it doesn't even get called so the activity view controller is not finished initializing?
Okay, finally found the culprit, somebody tipped me of that i should look into extension of UIViewController to check if i'm overriding something, well i didn't but i did override UILabel and UIButton's awakeFromNib and setNeedsDisplay because my app have multiple language support and In-App change language support and i wanted to "automate" UILabel and UIButton to change language font(because some font is better looking) so that i can avoid attaching listener to viewcontrollers to change language font when they do in-app language changes.
override open func awakeFromNib() {
super.awakeFromNib()
if let prefLang = UserDefaults.languageCode{
self.font = switchFontForLang(lang: prefLang)
}
}
override open func setNeedsDisplay() {
super.setNeedsDisplay()
if let prefLang = UserDefaults.languageCode{
self.font = switchFontForLang(lang: prefLang)
}
}
Particularly the setNeedsDisplay() is causing problem, i've put a debugmessage in them both and i found out its being called endlessly, my guess is because the "in-app" language font changing is trying to change language to the setting but the UIActivityViewController is somehow trying to change the font back or at the very least calling setNeedsDisplay() when it detects something is not right, which it will call into the overrided method and then it will detect it back again thus creating a loop of endlessly calling setNeedsDisplay().
I'm trying to create a UITableView that contains a bunch of cells that count down from a value. Initially I was just running an NSTimer in the ViewController on repeat to run a function that would update the labels and then run a reloadData on the table, and that does technically work, but with some issues.
To fill things in a bit more, I'm well aware NSTimer isn't accurate as a timer, so I use a date calculation for the time on the labels. I also considered using CADisplayLink so the refresh would sync with the screen, but this ran into problems with scrolling the table, making it very jumpy. Another issue is when issuing the Edit command on the table, the UI is unable to delete rows because of the refresh.
So, I've since considered moving my Timer into each UITableViewCell, and I can tell that it is running via the console, but the table doesn't update. This makes sense, given that it can't run the reloadData on the table, but I'm a bit stuck as to where to go next.
I found a similar thread here and even tried the AsyncTimer suggested here but either I'm doing something wrong, or these aren't quite the same issues? Any help is appreciated, thank you!
var timer = Timer()
self.timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(self.checkTimers), userInfo: nil, repeats: true)
#objc func checkTimers() {
for counter in counters {
if counter.isActive {
counter.updateSeconds()
}
}
tableView.reloadData()
}
There are 2 ways to get CADisplayLink in iOS. The direct one is to use initializer:
let displaylink = CADisplayLink(target: self,
selector: #selector(step))
Returns a new display link.
This way is used in Apple's example: Listing 1.
But there are other way to get it from UIScreen:
let displayLink = UIScreen.main.displayLink(withTarget: self,
selector: #selector(step))
Returns a display link object for the current screen.
You use display link objects to synchronize your drawing code to the screen’s refresh rate. The newly constructed display link retains the target.
Documentation is very poor for details, but the second way looks a little more optimised. May be someone who has experience with CADisplayLink can tell which way to create it is preferred.
I have a pretty simple app, with a couple of view controllers. There is a MKMapView in the second view controller. It is set up correctly, and functions fine. The problem is, each time I load its view the Memory usage jumps ~30mb, and never goes back down, so each time i go into the view it keeps jumping and eventually gets super high.
I tried removing the map view when i leave the controller like this:
override func viewWillDisappear(animated: Bool) {
map.removeFromSuperview()
}
but it doesn't have any effect on the memory. The map views delegate is set to its view controller.
I tried checking for leaks using Xcode instruments but didn't find anything.
Does anyone know how to fix this?
Thanks
EDIT:
Adding this seems to work:
func removeNastyMapMemory() {
map.mapType = MKMapType.Hybrid
map.delegate = nil
map.removeFromSuperview()
map = nil
}
override func viewWillDisappear(animated: Bool) {
removeNastyMapMemory()
}
This is not Swift issue, is coming from Objective-C days. The possible ways to handle this issue is depending upon the situation and behavior of the app.
If you're using a Map for multiple times (or places), only create a single (shared) instance of it. Which you can use it whenever you want.
Or If you're only using it for once, then try a solution from here, https://stackoverflow.com/a/25419783/1603234. This may help. Reduce little. But not all.