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().
Related
I have created a custom UIActivity and it was working, and I presented that UIActivityController as per typical tutorials (e.g. here). When configuring that controller I disabled basically all the services:
let items = [location]
let googlemaps = GoogleMapsActivity()
let applemaps = AppleMapsActivity()
let ac = UIActivityViewController(activityItems: items, applicationActivities: [googlemaps, applemaps])
ac.excludedActivityTypes = [.addToReadingList, .assignToContact, .markupAsPDF, .openInIBooks, .postToFacebook, .postToFlickr, .postToVimeo, .postToWeibo, .postToTwitter, .postToTencentWeibo, .print, .saveToCameraRoll]
present(ac, animated: true)
Then I might have stumbled onto an iOS Bug?
When that activity view controller was visible I tapped on "More", which gave me a list of my 2 custom activities. There was no UISwitch to turn them on an off, but there was a typical "row handle" as in a UITableView. I was testing, and tried re-arranging rows. This made one of the activities disappear from that list and now that activity is gone forever.
It won't appear in a list again, even if I delete and re-install the app. It seems I permanently removed the ability for this iOS device to make use of that UIActivity.
What have I done wrong or how can I fix it?
It might have had something to do with these 2 custom activities ultimately sharing the same UIActivity.ActivityType rawValue. I refactored that so they have different values and the issue doesn't seem to occur anymore, or I haven't been able to reproduce it.
I have an app with a UITabBarController containing 5 items. In the last item (profile), the user can log out or delete his account and will be automatically redirected to the OnBoarding screen :
func signout(ofViewController sender: UIViewController, action: ENLoginScreenAction) {
let onBoardingVC = ENOnBoardingViewController()
onBoardingVC.withAction = action
onBoardingVC.modalPresentationStyle = .formSheet
ENUserInstance.userLogout()
sender.present(onBoardingVC, animated: true)
}
I don't understand very well the UIView lifecycle notion so it appears that after logout, none of my 5 UITabBarController children are deinited.
I am pretty sure I am missing something about it so is there a way to deinit those children or is it normal to not do it after a logout or something similar in term of application lifecycle?
You right they are still here. And it's fine to have them there, as long as user can't access them if they are not supposed to. Don't worry about the memory, it's very light (depending actually on what you have in it).
The os will deinit them if it considers that they are not useful anymore, which can be the case if they are not referenced anymore. Your tabbarcontroller might reference them, so that's maybe why they are not deinit
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.
I am using an instance of UIDocumentInteractionController to offer the user the option to open a given document, if a capable app is installed on their device.
Apple's documentation for the QuickLook Framework mentions that:
To display a Quick Look preview controller you can use any of these
options:
Push it into view using a UINavigationController object.
Present it modally, full screen, using the presentModalViewController:animated:
method of its parent class, UIViewController.
Present a document
interaction controller (as described in Previewing and Opening Files.
The user can then invoke a Quick Look preview controller by choosing
Quick Look from the document interaction controller’s options menu.
(emphasis mine)
I am opting for that third option: Instead of using a QLPreviewController, I am presenting an UIDocumentInteractionController; this is my code:
#IBAction func openDocument(sender: AnyObject) {
let interactionController = UIDocumentInteractionController(URL: documentURL)
interactionController.delegate = self
// First, attempt to show the "Open with... (app)" menu. Will fail and
// do nothing if no app is present that can open the specified document
// type.
let result = interactionController.presentOpenInMenuFromRect(
self.view.frame,
inView: self.view,
animated: true
)
if result == false {
// Fall back to options view:
interactionController.presentOptionsMenuFromRect(
self.view.frame,
inView: self.view,
animated: true)
}
}
The fallback path gets executed (options menu), because I don't have any app that can open docx. However, the mentioned "Quick Look" option is not present:
What am I missing?
NOTE: I am not implementing any of the methods of UIDocumentInteractionControllerDelegate.
Silly me... again.
ANSWER: It turns out that, to have the QuickLook option present, you need to implement this method of the delegate protocol:
func documentInteractionControllerViewControllerForPreview(controller: UIDocumentInteractionController) -> UIViewController {
return self // returning self seems to work
}
(I somehow missed this. On a first read, I thought implementing this method meant that I should return a view controller capable of displaying the content -i.e. a full-fledged docx renderer, in this case. It just asks for a "source" from which to show the preview)
Once I implemented that method, the eye button started to appear in the options menu.
But on clicking it, my app would crash: by the time the quick look launches, the UIDocumentInteractionController was deallocated. I changed it from local variable to property, and now it works.
I'm new to swift and ios programming in general. I'm trying to display a modal view when my app first loads which it does. The problem I'm running into is that my modal keeps appearing over and over and over. Not sure where I'm going wrong.
BONUS QUESTION: Ultimately I'd like this to only happen the first time the user opens the app.
class ViewController: UIViewController {
var introModalDidDisplay = false
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
showIntroModal()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func showIntroModal() {
if (!introModalDidDisplay) {
println(introModalDidDisplay)
introModalDidDisplay = true
let intro = self.storyboard?.instantiateViewControllerWithIdentifier("introModal") as IntroModalViewController
intro.modalPresentationStyle = UIModalPresentationStyle.FormSheet
self.presentViewController(intro, animated: true, completion: nil)
}
}
}
Found it. My "intro" class was extending ViewController rather than UIViewController...apparently that's bad. Thanks for the help! Sorry for the wild goose chase.
When you close the modal view you show your ViewController view again, firing viewDidAppear once more and entering an infinite loop of showing your modal view, since the first view is always "appearing"
I'd suggest doing this in viewDidLoad, as the view is supposed to load only once. Try and experiment with these events and see when they are fired.
As for firing only once I'd suggest setting a flag in localStorage (plist) indicating whether it's the first time the user opens the app or not. For example set a flag in the first view's viewDidLoad and if that flag is false show your modal view and set the flag to true.
Here's a question about writing in plists in Swift: Save Data to .plist File in Swift
A couple of observations:
Are you saying that you're seeing this appear again and again while you're using the app? That would suggest that you have multiple instances of this view controller instantiated. For example, you might be doing a segue back to this view controller (which will create new instance) rather than unwinding/popping/dismissing back to it (which will return to the previous instance).
I'd suggest you have a breakpoint or logging statement in viewDidLoad and confirm that you see this once and only once. If you're seeing it multiple times, that means that you have some circular reference amongst your storyboard scenes (and, BTW, you are abandoning memory, a type of leak).
To handle this only presenting itself once between uses of the app, you need to save this introModalDidDisplay in some form of persistent storage. Often NSUserDefaults is used for this. For example, define introModalDidDisplay to look up the status in the standard user defaults:
var introModalDidDisplay = NSUserDefaults.standardUserDefaults().boolForKey("introModalDidDisplay")
Then your showIntroModal can update this setting in the user defaults:
func showIntroModal() {
if !introModalDidDisplay {
introModalDidDisplay = true
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "introModalDidDisplay")
NSUserDefaults.standardUserDefaults().synchronize()
let intro = self.storyboard?.instantiateViewControllerWithIdentifier("introModal") as IntroModalViewController
intro.modalPresentationStyle = UIModalPresentationStyle.FormSheet
self.presentViewController(intro, animated: true, completion: nil)
}
}
Clearly, you can use whatever persistent storage technique you want (plist, archive, user defaults, Core Data, SQLite), but the idea is the same: Retrieve the status from persistent storage and once the intro screen has been presented, update that persistent storage accordingly.
By the way, by looking this up in persistent storage, we also fix the symptom of the problem I discussed in point #1. But you really want to fix the root cause of that first point, too, because otherwise you'll be leaking memory (if, of course, you're really instantiating multiple copies of the ViewController class).
By the way, looking ahead to the future, rather than storing just a boolean, I might suggest storing a version number identifier, too. That way, when you release version 2.0 of the app, you'll be able to decide whether the v1.0 users might see the updated intro screen again (or perhaps a custom one that focuses on what's new).