Force focus (and restore if minimized) iOS App on macOS - ios

So far I have successfully adapted my app to run on macOS. But there is one thing missing, I want to force-focus my application when a certain notification is triggered.
I have tried using the UiWindow property windowLevel and the method makeKeyAndVisible but they didn‘t do anything.
Does somebody know what else I could try to get my app focused and restored (if it got minimized) ?

It seems like UIWindow.makeKeyAndVisible is ignored in catalyst, but activating the UISceneSession that contains the window does what you're after.
if let session = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }.first {
UIApplication.shared.requestSceneSessionActivation(session, userActivity: nil, options: nil, errorHandler: nil)
}
Note that calling requestSceneSessionActivation with nil as the first argument will just open a new window.

Related

Alternative to AVSystemController_AudioVolumeNotificationParameter post iOS 15?

The approved KVO approach for responding to device volume level changes stops detecting volume button presses after min/max outputVolume is reached. I'd like to continue to receive those button press events after min/max, so I assume I need to try this solution, even if it's not supported by Apple. However, I'm very much an amateur iOS programmer so I could use a hint. Here's what I've been doing (using RxSwift):
NotificationCenter.default.rx.notification(Notification.Name(rawValue: "AVSystemController_AudioVolumeNotificationParameter"))
.subscribe(onNext: { [weak self] notification in
guard let my = self else { return }
my.volumeNotification.accept(notification.userInfo!["AVSystemController_AudioVolumeNotificationParameter"] as! Double)
})
.disposed(by: disposeBag)
Should I instead be subscribing to a Notification named "MPVolumeControllerDataSource_SystemVolumeDidChange"?
Thanks in advance!
Three cheers for open source, specifically: JPSVolumeButtonHandler. This component works like a champ, and uses the Apple-approved KVO technique. Be aware that this component sets AVAudioSession options to .mixWithOthers which prevents MPRemoteCommandCenter from receiving/handing any BlueTooth commands. So if you need BT (Swift 5):
let volumeButtonHandler = JPSVolumeButtonHandler(up: {
// handle up press
}, downBlock: {
// handle down press
})
volumeButtonHandler.sessionOptions = [] // allow remote BT
I also found that programmatically setting the device volume to 0.5 before initializing the button handler avoided occasional min/max barriers. If the device initial volume was close to the min or max, the handler would stop after a few button presses:
try AVAudioSession.sharedInstance().setActive(true, options: [])
MPVolumeView(frame: .zero).volumeSlider.value = 0.5

Show window that covers everything when apps enters background

In iOS12 and below I used to use something similar to this to show a window on top of everything to cover my app contents. This use to work but in iOS13 betas this does not work anymore.
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var coverWindow: UIWindow?
func applicationDidEnterBackground(_ application: UIApplication) {
if self.coverWindow != nil {
// Skip since cover window is already showing
return
}
let vc = UIViewController()
let label = UILabel(frame: window!.bounds)
label.text = "CoverWindow. Tap to app see contents"
vc.view = label
vc.view.backgroundColor = UIColor.lightGray
let coverWindow = UIWindow(frame: window!.bounds)
coverWindow.rootViewController = vc
coverWindow.windowLevel = .alert
coverWindow.makeKeyAndVisible()
self.coverWindow = coverWindow
}
}
Apparently window changes are not reflected in screen until app enters foreground again.
Question
Does anyone know how fix or workaround this? or maybe this approach is incorrect?
Any help would be highly appreciated
Notes
I don't use a simple view because my app might be showing other windows too and my requirement is to cover everything.
I don't use applicationWillResignActive because we want to only show coverWindow when it enters background. (TouchID authentication and other stuff might trigger applicationWillResignActive and coverWindow would incorrectly show)
Example code
Download Full working example code in Github (Run in iOS simulator 12 and 13 to see the difference)
You have to implement application life cycle, you just delete it , add those app life cycle functions and implement your codes , it ll be run without error
Answer to myself.
I reported this to Apple and it was fixed in iOS 13.1 or so. Latest version of iOS13 does NOT have this bug :)

Swift pushViewController Works on iPad but not iPhone

I have no idea what is happening but my code stopped working on iPhone but works fine on iPad. (Using Xcode 10 and Swift 4)
I first check for the data and if not set it transfers to the ConfigVC.
Nothing happens on the simulator for any phone settings. I went so far as to reinstall my system software and Xcode.
I use this same approach on other apps and it is fine. For some strange reason it just stopped working on this app.
func dataCheck(){
// make sure the required data is set
if User.getInfo() == nil || Index.getAll() == nil {
let vc = UIStoryboard.init(name: "Config", bundle: Bundle.main).instantiateViewController(withIdentifier: "ConfigVC") as? ConfigVC
self.navigationController?.pushViewController(vc!, animated: true)
}
}
The code shows it just ending.
You're trying to push/present a vc while the current isn't yet loaded inside viewDidLoad so move the call to dataCheck inside viewWillAppear/viewDidAppear

Inconsistent value from NSUserDefaults at app startup

Under the iOS simulator, I'm having trouble getting a consistent value back from NSUserDefault on app startup. I've set the value in a previous run of the app (and I put some prints in there to make sure there was no accidental changes), so I don't think there's a problem with the way I'm calling synchronize(). The reads are happening on the main thread also, and after I've set the default values. After a couple of seconds, if I retry the fetch from NSUserDefaults, I get the correct value (I put a timer in there to verify that the value gets corrected and seems to stay the same after startup).
The value represents where the user will store the data (on iCloud or just on the local device) and is represented by an integer in the user defaults. Here's the enum representing the values:
class MovingDocument: UIDocument {
enum StorageSettingsState: Int {
case NoChoice = 0
case Local = 1
case ICloud = 2
}
}
Here's an excerpt of the class used to access NSUserDefaults:
class Settings {
static let global = Settings()
private enum LocalStoreKeys : String {
case IsHorizontal = "IsHorizontal"
case ItemInPortrait = "ItemInPortrait"
case RotateTitle = "RotateTitle"
case StorageLocation = "StorageLocation"
}
// Other computed variables here for the other settings.
// Computed variable for the storage setting.
var storageLocation: MovingDocument.StorageSettingsState {
get {
print("Storage state fetch: \(NSUserDefaults.standardUserDefaults().integerForKey(LocalStoreKeys.StorageLocation.rawValue))")
return MovingDocument.StorageSettingsState(rawValue: NSUserDefaults.standardUserDefaults().integerForKey(LocalStoreKeys.StorageLocation.rawValue)) ?? .NoChoice
}
set {
print("Setting storage location to \(newValue) (\(newValue.rawValue))")
NSUserDefaults.standardUserDefaults().setInteger(newValue.rawValue, forKey: LocalStoreKeys.StorageLocation.rawValue)
synchronize()
CollectionDocument.settingsChanged()
}
}
func prepDefaultValues() {
NSUserDefaults.standardUserDefaults().registerDefaults([
LocalStoreKeys.IsHorizontal.rawValue: true,
LocalStoreKeys.ItemInPortrait.rawValue: true,
LocalStoreKeys.RotateTitle.rawValue: true,
LocalStoreKeys.StorageLocation.rawValue: MovingDocument.StorageSettingsState.NoChoice.rawValue
])
}
func synchronize() {
NSUserDefaults.standardUserDefaults().synchronize()
}
}
The value that I should be reading (and which I always seem to get after startup) is the value '1' or .Local. The value that I am sometimes getting instead is '0' or .NoChoice (which just happens to be the default value). So, it looks like it's using the default value until some later point in the application startup.
For completeness sake, here's the relevant app delegate code:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
Settings.global.prepDefaultValues() // Must be called before doing CollectionDocument.start() because we need the default value for storage location.
CollectionDocument.start()
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
browserController = ViewController()
navController = UINavigationController(rootViewController: browserController!)
navController!.navigationBar.translucent = false
self.window!.rootViewController = navController
self.window!.makeKeyAndVisible()
checkStorageLocation()
return true
}
The getter is called within CollectionDocument.start() (CollectionDocument is a subclass of MovingDocument, where the location enum is defined). The call at the end to checkStorageLocation() is my periodic debugging check for the value of the storage location (it calls itself back via dispatch_after()).
As a side note, I also tried putting most of the code in the application(didFinishLaunchingWithOptions...) into a dispatch_async call (on the main thread) to see if that made a difference (just the call to set up the default values and the return statement were done immediately, the rest of the calls were put into the asynchronous call block), and it still got the wrong value (sometimes). I guess I could try using dispatch_after instead (and wait a second or two), but I can't really start the app until this is complete (because I can't read the user data from the doc until I know where it is) and a loading screen for just this one problem seems ridiculous.
Any ideas about what might be going wrong? I'm unaware of any limitations on NSUserDefaults as far as when you are allowed to read from it...
Rebooting my mac fixed the problem (whatever it was). iOS 10, NSUserDefaults Does Not Work has a similar problem and the same solution (though the circumstances are quite different, since I have tried neither Xcode 8 nor iOS 10).

iOS: how to delay the launch screen?

When launch an app, the LaunchScreen.xib is removed as soon as all the assets are initialized.
I want to make the launch screen stay for at least 1 sec.
Is there a way to achieve this?
Thank you!
You can create a view controller that uses the LaunchScreen storyboard, present it (not animated) on applicationDidFinishLaunching or applicationWillFinishLaunching, and dismiss it whenever you want.
Keep in mind this is discouraged by Apple because it gives the impression that your app takes a lot longer to launch, which is bad user experience and might cause some of your users to delete your app.
Swift 4 Update
Just write one line of code
Thread.sleep(forTimeInterval: 3.0)
in the method of didfinishLauching.... in appdelegate class.
Example
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
Thread.sleep(forTimeInterval: 3.0)
// Override point for customization after application launch.
return true
}
Never sleep on the main thread. Doing this could actually cause iOS to kill your app for taking too long to start up
Thought I chip in my thoughts on this, I wanted to write in comments but it won't allow many lines. I believe many apps developer want to do this (delay the launch screen) is because they want to build a brand presence of the apps/games for their company.
Having said that, launch screen is NOT designed for that, as Rick Maddy explained in the comment section in one of the other answers. Launch screen's purpose is to make users feel the app is instantly running by showing the empty UI while the actual data is loading at the back (willAppear and so on).
So to achieve what many developers want, while being in-line with Apple's HIG, what you can do is:
Display UI template in the launchscreen as intended by Apple HIG.
Upon main screen load, load up another VC that shows "intro" of your brand. Make sure this runs only ONCE (a simple flag in
NSUserDefaults should do the trick).
Users should be allowed to skip this if it is a long "intro".
The same "intro" VC should be available to user by tapping on a "View Intro" button somewhere (maybe in about page).
If you want to go with simple, you can use NSThread:
[NSThread sleepForTimeInterval:(NSTimeInterval)];
You can put this code into the first line of applicationDidFinishLaunching method.
For example, display default.png for 1.0 seconds.
- (void) applicationDidFinishLaunching:(UIApplication*)application
{
[NSThread sleepForTimeInterval:1.0];
}
It will stop splash screen for 1.0 seconds.
Alhamdulellah Solution is find
Only copy and paste this code in AppDelegate Class
Call this SplashScreenTiming() in didFinishLaunchingWithOptions()
private func SplashScreenTiming(){
let LunchScreenVC = UIStoryboard.init(name: "LaunchScreen", bundle: nil)
let rootVc = LunchScreenVC.instantiateViewController(withIdentifier: "splashController")
self.window?.rootViewController = rootVc
self.window?.makeKeyAndVisible()
Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(DismissSpalshController), userInfo: nil, repeats: false)
}
#objc func DismissSpalshController(){
let mainVC = UIStoryboard.init(name: "Main", bundle: nil)
let rootvc = mainVC.instantiateViewController(withIdentifier: "SignInVC")
self.window?.rootViewController = rootvc
self.window?.makeKeyAndVisible()
}

Resources