Whenever I swipe outside of my iMessage app to open another iMessage app, and then come back to my iMessage app by swiping back, my iMessage app gets loaded again.
Even viewDidLoad of MessagesViewController gets called again.
My code in MessagesViewController looks like-
class MessagesViewController: MSMessagesAppViewController {
override func viewDidLoad() {
super.viewDidLoad()
openSuitableController()
}
override func willTransition(to presentationStyle: MSMessagesAppPresentationStyle) {
removeAllChildViewControllers()
}
override func didTransition(to presentationStyle: MSMessagesAppPresentationStyle) {
openSuitableController()
}
}
In openSuitableController, I just add a childViewController to MessagesViewController.
Because the apps start again, the collectionView gets loaded and scrolled to the top and it leads to flickering.
Please do comment if you need more info.
PS: I have added Objective-C tag because I don't feel this problem is due to specific language.
I believe this is unavoidable and is just the way that iMessage manages the iMessage apps that it hosts. i.e.: The iMessage framework closes your app when you swipe out and starts up the next one you swipe into. This is true with iOS 11 and prior, it might change in the future of course.
iMessage has a rather complicated way of presenting your UI by embedding it in its own sandboxed container view controller hierarchy, this is to ensure secure trust boundaries between iMessage itself and the private chat data and your application. As a result, there is more overhead in presenting your app than if your app was starting up by itself, my guess is that apps are restarted like this in order to minimise resource usage and to protect iMessage from running out of the resources it needs.
It's worth keeping your app startup as quick as possible (for example by limiting dynamic library loading time) and keep in mind how your initial content will look as it is swapped for the stored snapshot.
I believe its really important to minimise the work that is done when your app starts and when it stops in order to allow the smoothest transitions between apps.
When you swipe out of an app to another one iMessage makes a snapshot image of the previous UI state and then at the time of swiping back in to your app it displays that snapshot while your app goes through its initialisation.
So it pays to look carefully at the snapshot process and try to make sure your initial UI presentation will transition well from the snapshot that is made by the iMessage framework.
Related
I'm having an issue that exclusively occurs when I launch my app after it was suspended (while in background) by iOS. In any other situation, the issue won't occur - which already is quite weird. As using the XCode debugger prevent suspension of the app when it goes to background, I have been collecting logs through the Unified Logging system to investigate.
The logs indicate that when I launch the app after suspension by the system, the storyboard scene that is defined as Initial View Controller does get displayed but the code that sits in the viewDidLoad() function of the associated view controller class is not being run.
This happens exclusively after suspension by the system, and will never happen if I manually (or through the XCode stop button) kill the app and relaunch it.
I'm totally stuck as I don't understand why this is happening and why there would be a difference induced by the system suspension.
My project is declaring the main storyboard as "Main Interface" in the target settings. In this storyboard, a scene is defined as Initial View Controller. This scene has a custom class defined, and when I click the arrow next to the custom class name, XCode does take me to the class declaration code.
The class declaration code looks like this:
import UIKit
import SwiftUI
import Firebase
import os.log
class InitialLoadViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//custom code
}
}
The custom code starts with a logging statement that I don't see printed when the issue occurs.
Hope someone may have an idea about what's happening!
thank you 🙏
I could be completely wrong, but I believe that while your app is not getting reloaded, it is disappearing. Try using viewDidAppear or viewWillAppear instead of viewDidLoad for the code you want to be executed after suspension.
After a lof of debugging I found out my issue was related to some of my app background activities. My app is sometimes receiving silent push notifications that wakes it up in the background to perform a network call that refreshes some local data. This process in itself was working well, but waking up my app in the background was also instantiating some view controllers (ie starting up my UI). But my UI has some dependancy to additional network calls that were not designed to work while the app is active in the background. They were therefore failing and leaving the UI in a fail condition that was not handled when the app is in background.
So I have now basically conditioned some aspects of launching my UI to having my app active in the foreground, rather than just having my app active.
for the app I'm developing, I implemented a lock screen that allows the user to unlock the App by manual-pin or touch/Face-ID.
Everything is working OK during the normal use.
However, I need to show the lock screen when the app is resumed from background and even in the task switcher to avoid "peeking" at the content without having unlocked properly.
As recommended by Apple in this (old) article, I present the lock view controller in applicationDidEnterBackground:
func applicationDidEnterBackground(_ application: UIApplication) {
let lockVC = LoginViewController()
lockVC.loginType = LoginViewController.LoginType.resumeApp
if let topViewController = UIApplication.topViewController() {
topViewController.present(lockVC, animated: false, completion: nil)
}
}
where topViewControler is a useful extension to determnine the topmost view controller: Get top most UIViewController
.
The lockVC.loginType = ... is just to let the ViewController what type of login I need and customize its view a little bit
The results I get are a bit weird, both on simulator and real devices:
the task switcher preview for my app is completely black
when the app is resumed, the screen remains black as in preview and unresponsive. Only way to exit is to kill the app.
before obtaining the weird results above, I had to access all outlets as optional to avoid termination... that's fine for viewDidLoad stuff (I didn't expect the need for this when entering background, since the view had been loaded before - outlet wired) but the strange thing is that I had the same error for an IBAction in viewDidAppear (IBAction for touch-id button called automatically if touch/face-id is available - just a requirement).
I believe I'm missing something big here... but no other hints found.
No one of the ready-made lock screen solutions come with an example for the background/taskSwicth/resume case).
Note that the black/unresponsive screen seems to be same both if I use the mentioned extension for the topmost view controller to present or if I simply try to present it by
self.window?.rootViewController?.present(lockVC, animated: false)
(which I believe is wrong, but tried anyway)
Any help would be apreciated
I found a temporary workaround:
Display the lock screen when the app becomes active from background, as suggested by Dev_Tandel
Hide information in the task switcher by adding a blur effect on the current screen where the app is sent to background (applicationWillResignActive) and removing it when the app comes active again (applicationDidBecomeActive), as suggested here
As said, it's a temporary workaround that i wanted to share, but I don't like this 100%. It is required to show the lock screen in the task swtcher and Ive seen apps doing it (e.g. "oneSafe").
Still looking for help.
Solved, thanks to this post.
Apparently I was naive in trying to present a view controller with just its object instance.
If I use
let lockVC = topViewController.storyboard?.instantiateViewController(withIdentifier: "IDLoginViewController") as! LoginViewController
everything works, the lock screen is shown also in the task switcher, no need to blur!
The multitasking features got updates in iOS 11, one of those was slide over which is demonstrated in the gif below.
With these changes it's no longer possible to use the techniques that check frame size from iOS 9 to detect if another app is a "slide over" over my app.
Is there any new method to detect if another app is running as slide over?
I was able to get this working fairly easily on an iPad Pro (which supports side-by-side apps, not just slide-overs). Here's the code:
class ViewController: UIViewController {
override func viewWillLayoutSubviews() {
isThisAppFullScreen()
}
#discardableResult func isThisAppFullScreen() -> Bool {
let isFullScreen = UIApplication.shared.keyWindow?.frame == UIScreen.main.bounds
print("\(#function) - \(isFullScreen)")
return isFullScreen
}
}
The end result is that it will print "true" if the view is full screen and "false" if it's sharing the screen with another app, and this is run every time anything is shown, hidden, or resized.
The problem then is older devices that only support slide-over. With these, your app is not being resized anymore. Instead, it's just resigning active use and the other app is becoming active.
In this case, all you can do is put logic in the AppDelegate to look for applicationWillResignActive and applicationDidBecomeActive. When you slide-over, you get applicationWillResignActive but not applicationDidEnterBackground.
You could look for this as a possibility, but you cannot distinguish between a slide-over and a look at the Notifications from sliding down from the top of the screen. It's not ideal for that reason, but monitoring application lifecycle is probably the best you can do.
I have "Tinder" like swipping view that is located in a CardViewController. The card View Controller is accessed by moving through two other view controllers. i.e. Load App -> FirstViewController -> SecondViewController - > CardViewController.
When I am in the Card ViewController and I go into background mode, the app launches on the FirstViewController and on going to the cards, they are loaded from the first card in a stack of about 10?
Is there anyway to load the app from the last Card I had swipped and in the CardViewController without having to navigate from the FirstView Controller again?
I would really appreciate the help as it's horribly affecting some of my users.
An example of a Tinder like card view is shown!
The problem, from the sound of it, is not what happens when the app goes into the background — that would leave it in exactly the same state when it reactivates. The problem is what happens when the app goes into the background and quits. Your app is then relaunched from scratch, which is why you find yourself in the first view controller. What's upsetting you is the difference between the app's behavior in these two situations.
Apple provides a solution to this situation: UIViewController, along with the App Delegate, has methods permitting you to save and restore state. When the app goes into the background, the current configuration (what view controller's view is showing) is saved. That way, even when the app quits, when it relaunches it can get back to that configuration before it appears to the user. Thus, coming back from background-and-quit looks just like coming back from mere backgrounding.
For full details, see Apple's documentation. This is a good place to start:
https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/PreservingandRestoringState.html
My team is developing a set of SDKs for barcode scanning, ID scanning and OCR. We use device's camera, specifically, AVCaptureSession, to obtain video frames on which we perform our processing.
We're exploring new iOS 9 multitasking features Slide Over and Split View.
Apple suggests opting out of these features for camera-centric apps, where using the entire screen for preview and capturing a moment quickly is a primary feature (reference). This is the approach the use in their sample app AVCam.
However, our customers might have apps which don't fall into this category (e.g Mobile banking apps), so we cannot force them to opt out, instead, we need to handle new features in the SDK. We're exploring what would be the best approach to do that, since the docs at the moment aren't really telling us what to do.
We used our simple Camera sample app to analyse the use case. The sample app is available on Github and it's developed as of iOS 9 Beta 5.
From the sample app, it can be clearly seen which system events happen when Slide Over is used, and when Split View is used.
When our app is primary, and Slide Over is used, we get UIApplicationWillResignActiveNotification and AVCaptureSessionDidStopRunningNotification
When Slide Over is used, and our app is secondary, we get UIApplicationWillEnterForegroundNotification and AVCaptureSessionDidStopRunningNotification immediately after that
When Split View is used, on each divider drag, our app gets UIApplicationWillResignActiveNotification.
However, if the Camera is launched when in Split View, it immediately gets AVCaptureSessionDidStopRunningNotification
So, empirically, it looks like AVCaptureSession is immediately stopped when Slide Over or Split View are used.
What's confusing is that UIImagePickerController, which our sample app also supports, exhibits completely different behaviour.
UIImagePickerController isn't stopped when the app goes into Slide Over/ Split View, instead, it functions completely normally. One can normally take a photo in Split View. In fact, two apps, both of which present UIImagePickerController, can work side by side, with UIImagePickerController of the active app being active. (You can try that by running our sample app, and Contacts app -> New Contact -> Add photo)
With all this in mind, our questions are the following:
If AVCaptureSession is immediately paused when Slide Over and Split View are used, is it a good idea to monitor AVCaptureSessionDidStopRunningNotification, and present a message "Camera Paused" to the user, so that he clearly knows that the app isn't performing scanning?
Why is behaviour of UIImagePickerController different than AVCaptureSession?
Can we expect from Apple than in future beta versions behaviour of AVCaptureSession changes to match UIImagePickerController?
In case you haven't found out yet. After some more investigation I can now answer your first question:
If AVCaptureSession is immediately paused when Slide Over and Split
View are used, is it a good idea to monitor
AVCaptureSessionDidStopRunningNotification, and present a message
"Camera Paused" to the user, so that he clearly knows that the app
isn't performing scanning?
The notification you actually want to observe is this one: AVCaptureSessionWasInterruptedNotification
And you want to check for the newly introduced in iOS9 reason: AVCaptureSessionInterruptionReason.VideoDeviceNotAvailableWithMultipleForegroundApps
override func viewWillAppear(animated: Bool)
{
super.viewWillAppear(animated)
self.addObserverForAVCaptureSessionWasInterrupted()
}
func addObserverForAVCaptureSessionWasInterrupted()
{
let mainQueue = NSOperationQueue.mainQueue()
NSNotificationCenter.defaultCenter().addObserverForName(AVCaptureSessionWasInterruptedNotification, object: nil, queue: mainQueue)
{ (notification: NSNotification) -> Void in
guard let userInfo = notification.userInfo else
{
return
}
// Check if the current system is iOS9+ because AVCaptureSessionInterruptionReasonKey is iOS9+ (relates to Split View / Slide Over)
if #available(iOS 9.0, *)
{
if let interruptionReason = userInfo[AVCaptureSessionInterruptionReasonKey] where Int(interruptionReason as! NSNumber) == AVCaptureSessionInterruptionReason.VideoDeviceNotAvailableWithMultipleForegroundApps.rawValue
{
// Warn the user they need to get back to Full Screen Mode
}
}
else
{
// Fallback on earlier versions. From iOS8 and below Split View and Slide Over don't exist, no need to handle anything then.
}
}
}
override func viewWillDisappear(animated: Bool)
{
super.viewWillDisappear(true)
NSNotificationCenter.defaultCenter().removeObserver(self)
}
You can also know when the interruption was ended by observing:
AVCaptureSessionInterruptionEndedNotification
Answer based on these two links:
http://asciiwwdc.com/2015/sessions/211
https://developer.apple.com/library/ios/samplecode/AVCam/Introduction/Intro.html
Since iOS 16.0+, it's possible to use isMultitaskingCameraAccessEnabled flag.
Reference
Since iOS 13.5+ and iPadOS 13.5+, it's possible to use entitlement com.apple.developer.avfoundation.multitasking-camera-access, allowing the app to continue using the camera while running alongside another foreground app.
Reference
More information about accessing the camera while multitasking
here