I am trying to integrate the Unity build for iOS into a pre-existing native app. I am using two buttons to start and stop the unity, but when I click on start, Unity view comes on the top of the current view and both the buttons disappear behind it. I used Unity 2019.1.11f1 for building the unity project.
Below is my App Delegate Code:
//
// AppDelegate.swift
// blue
//
// Created by Vikas Roy on 01/07/19.
// Copyright © 2019 MedleyOne. All rights reserved.
//
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var application: UIApplication?
#objc var currentUnityController: UnityAppController!
var isUnityRunning = false
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
self.application = application
unity_init(CommandLine.argc, CommandLine.unsafeArgv)
currentUnityController = UnityAppController()
currentUnityController.application(application, didFinishLaunchingWithOptions: launchOptions)
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
if isUnityRunning {
currentUnityController.applicationWillResignActive(application)
}
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
if isUnityRunning {
currentUnityController.applicationDidEnterBackground(application)
}
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
if isUnityRunning {
currentUnityController.applicationWillEnterForeground(application)
}
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
if isUnityRunning {
currentUnityController.applicationDidBecomeActive(application)
}
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
func startUnity() {
if !isUnityRunning
{
isUnityRunning = true
currentUnityController.applicationDidBecomeActive(application!)
}
}
func stopUnity() {
if isUnityRunning {
currentUnityController.applicationWillResignActive(application!)
isUnityRunning = false
}
}
}
Below is the View Controller Code:
//
// Unity3DViewController.swift
// blue
//
// Created by Vikas Roy on 25/07/19.
// Copyright © 2019 MedleyOne. All rights reserved.
//
import UIKit
class Unity3DViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
func showUnitySubView() {
if let unityView = UnityGetGLView() {
view?.insertSubview(unityView, at: 0)
}
}
#IBAction func StartUnity(_ sender: Any) {
if let appDelegate = UIApplication.shared.delegate as? AppDelegate
{
appDelegate.startUnity()
showUnitySubView()
}
}
#IBAction func StopUnity(_ sender: Any) {
if let appDelegate = UIApplication.shared.delegate as? AppDelegate
{
appDelegate.stopUnity()
}
}
}
I want buttons to always stay on the top of the unity view. Any idea where the issue can be?
In ViewContoller, where Unity is loading,
create an outlet of your button
and call this function in viewDidload() this way:
let unityView = UnityGetGLView()
unityView.addsubView(yourbutton)
Related
I have a class with properties updated in viewController. I wanted to save the properties when the app goes into background or quit using AppDelegate. I used the following codes but it appears that the properties were not passed to the AppDelegate. Furthermore the applicationWillTerminate codes did not seem to get executed.
// testClass is defined and the properties are updated in viewController, e.g
testClass.status = true // default is false
// I want to save testClass.status when the app goes into background or being terminated using the following:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var vc = ViewController()
func applicationDidEnterBackground(_ application: UIApplication) {
print(vc.testClass.status) // prints false
//codes to save
}
// save before App is terminated
func applicationWillTerminate(_ application: UIApplication) {
print(vc.testClass.status) // this code did not get executed?
//codes to save
}
}
applicationWillTerminate is called only when a user terminates the app without switching it to background mode.
When the app is active, double press on Home button and terminate the app.
But if you switch the app to the background, and then try to terminate the app, applicationWillTerminate will not be called.
And you are creating an instance of ViewController in AppDelegate
var vc = ViewController()
If you change the testClass property in another ViewController class instance, you won't get that value here. So create a singleton class like this
class TestClass: NSObject {
static let shared = TestClass()
private override init() {
super.init()
}
var status = false
}
Now update the value in any view controller from the singleton class
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
TestClass.shared.status = true
}
}
In AppDelegate save and retrieve the value from UserDefaults
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
TestClass.shared.status = UserDefaults.standard.bool(forKey: "Status")
return true
}
func applicationDidEnterBackground(_ application: UIApplication) {
UserDefaults.standard.set(TestClass.shared.status, forKey: "Status")
}
func applicationWillTerminate(_ application: UIApplication) {
UserDefaults.standard.set(TestClass.shared.status, forKey: "Status")
}
}
Or create a computed property to save the value in UserDefaults whenever it is changed.
class TestClass: NSObject {
static let shared = TestClass()
private override init() {
super.init()
}
var status: Bool {
get {
return UserDefaults.standard.bool(forKey: "Status")
}
set {
UserDefaults.standard.set(newValue, forKey: "Status")
}
}
}
As already mentioned by others you can ignore applicationWillTerminate.
To get notified when the app goes into the background just add an observer in the view controller.
However rather than didEnterBackground I'd recommend to observe willResignActive.
Add the observer in viewDidLoad once
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(forName: UIApplication.willResignActiveNotification, object: nil, queue: nil) { notification in
// save the properties
}
}
Or if you are using multiple view controllers you can add the observer in viewWillAppear and remove it in viewDidDisappear
Side note:
Never create a view controller with the default initializer ViewController() if you are using storyboard. You'll get a brand new instance which is not the storyboard instance.
I'm trying to set my application as a way to just start ringing in the background and get a notification when the user hit the shoulder button or home button(not all the time) and I don't want using background mode. So I coded this in swift, it works when my app is working in foreground and background for 10 seconds and ranging won't be restarted when the user illuminates his phone's screen. First I started background task and then in app delegate I was trying to start ranging for the beacon in the immediate range. Can someone help me about this?
#Background Task
class BackgroundTask1 : NSObject {
private let application: UIApplication!
private var identifier = UIBackgroundTaskInvalid
init(application: UIApplication) {
self.application = application
}
class func run(application: UIApplication, handler: (BackgroundTask1) -> ()) {
let backgroundTask = BackgroundTask1(application: application)
backgroundTask.begin()
handler(backgroundTask)
}
func begin() {
print("begin")
self.identifier = application.beginBackgroundTask {
self.end()
}
}
func end() {
if (identifier != UIBackgroundTaskInvalid) {
application.endBackgroundTask(identifier)
}
identifier = UIBackgroundTaskInvalid
}}
#AppDelegate
func init_()
{
let uuidString = "43F2ACD1-5522-4E0D-9E3F-4A828EA12C24"
let beaconRegionIdentifier = "Hello"
let beaconUUID:UUID = UUID(uuidString:uuidString)!
beaconRegion = CLBeaconRegion(proximityUUID: beaconUUID, identifier: beaconRegionIdentifier)
beaconRegion.notifyEntryStateOnDisplay = true
locationManager = CLLocationManager()
if (CLLocationManager.authorizationStatus() != CLAuthorizationStatus.authorizedWhenInUse) {
locationManager!.requestWhenInUseAuthorization()
}
locationManager!.delegate = self
locationManager!.pausesLocationUpdatesAutomatically=false
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
init_()
locationManager?.startRangingBeacons(in: beaconRegion)
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
var timer = Timer()
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
BackgroundTask1.run(application:
application) { (BackgroundTask1_) in
DispatchQueue.global(qos: .default).async
{
DispatchQueue.main.async {
self.init_()
self.locationManager?.startRangingBeacons(in: self.beaconRegion)
}
}
}
}
In my application I'm using Firebase Messaging and I'm testing to receive notification.
I'm using Postman as Rest service to configure the notification's body like:
{
"to": "/topics/test",
"priority": "high",
"notification": {
"title": "Test",
"body": "New",
"badge": "0"
},
"data": {
"foo": "bar"
}
}
Certificate is ok. I don't understand how to start programmatically a ViewController looking at the data passed..For example if data contains:
"data": {
"foo": "viewcontroller1"
}
I'd like to start ViewController1 when user clicks on the notification.
I can only print data in AppDelegate? How can I use values passed?
This is my AppDelegate.swift:
import UIKit
import Firebase
import FirebaseMessaging
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
FIRApp.configure()
let notificationTypes : UIUserNotificationType = [UIUserNotificationType.Alert, UIUserNotificationType.Badge, UIUserNotificationType.Sound]
let notificationSettings = UIUserNotificationSettings(forTypes: notificationTypes, categories: nil)
application.registerForRemoteNotifications()
application.registerUserNotificationSettings(notificationSettings)
return true
}
// [START refresh_token]
func tokenRefreshNotification(notification: NSNotification) {
let refreshedToken = FIRInstanceID.instanceID().token()!
print("InstanceID token: \(refreshedToken)")
// Connect to FCM since connection may have failed when attempted before having a token.
connectToFcm()
}
// [START connect_to_fcm]
func connectToFcm() {
FIRMessaging.messaging().connectWithCompletion { (error) in
if (error != nil) {
print("Unable to connect with FCM. \(error)")
} else {
print("Connected to FCM.")
}
}
}
//Receive and handle messages
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
// Print message ID.
print("Value for foo -> \(userInfo["foo"])")
//start viewcontroller programmatically
}
func applicationWillResignActive(application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
func applicationDidEnterBackground(application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(application: UIApplication) {
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
}
Can someone please explain me please?
Lets handle the code in didReceiveRemoteNotification First we extract which view controller should we present:
let type = userInfo["foo"] as! String
if type == "viewcontroller1" {
// here we go to start the view controller
}
You will need to use helping method to find the top most view controller to present on top of it.
func getTopViewController()->UIViewController{
if var topController = UIApplication.sharedApplication().keyWindow?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
return topController
// topController should now be your topmost view controller
}
return UIViewController()
}
To start a ViewController you should make an identifier for that in Storyboard. lets say its also called : viewcontroller1 then :
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("viewcontroller1") as! viewcontroller1
self.getTopViewController().presentViewController(vc, animated: true, completion: nil)
Note: When receiving the notification you'll need to check if the app was in background or it was in app or it was outside the app . For each one has different handling of how and when you'll need to show or present your view controller.
I'm trying to make my app download images in background. But when I press [Home] button, the app stop download. Is there any way to make it continue download even when I use another app? I have seen some apps can do like that but I don't know how.
This is what I've tried so far.
//
// AppDelegate.swift
// Swift-TableView-Example
//
// Created by Bilal ARSLAN on 11/10/14.
// Copyright (c) 2014 Bilal ARSLAN. All rights reserved.
//
import UIKit
import WebKit
protocol DownloadInBackgroundDelegate {
func downloadInBackgroundDidFinish(chapterid:Int, chaptername:String, storyid:Int, progressPercent:Float)
}
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var shareCache = NSURLCache()
var downloadDelegate:DownloadInBackgroundDelegate? = nil
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
var navigationBarAppearace = UINavigationBar.appearance()
application.setStatusBarOrientation(UIInterfaceOrientation.PortraitUpsideDown, animated: false)
self.startDownload()
return true
}
func application(application: UIApplication,
didReceiveRemoteNotification userInfo: [NSObject : AnyObject],
fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
}
func applicationWillResignActive(application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
func applicationDidEnterBackground(application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
FBAppEvents.activateApp()
}
func applicationWillTerminate(application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
}
func application(application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: NSError) {
}
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
}
func applicationDidReceiveMemoryWarning(application: UIApplication) {
NSURLCache.sharedURLCache().removeAllCachedResponses()
}
func application(application: UIApplication, willChangeStatusBarOrientation newStatusBarOrientation: UIInterfaceOrientation, duration: NSTimeInterval) {
application.windows
}
func startDownload(){
var filesPath = [String]()
filesPath.append("https://developer.apple.com/library/ios/documentation/iphone/conceptual/iphoneosprogrammingguide/iphoneappprogrammingguide.pdf")
filesPath.append("https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/MobileHIG/MobileHIG.pdf")
filesPath.append("https://developer.apple.com/library/ios/documentation/NetworkingInternetWeb/Conceptual/NetworkingOverview/NetworkingOverview.pdf")
filesPath.append("https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/AVFoundationPG/AVFoundationPG.pdf")
filesPath.append("http://manuals.info.apple.com/MANUALS/1000/MA1565/en_US/iphone_user_guide.pdf")
downloadFiles(0, filesPath: filesPath)
}
func downloadFiles(index: Int, filesPath: [String]) -> Void {
var imgURL: NSURL = NSURL(string: filesPath[index].stringByTrimmingCharactersInSet(.whitespaceAndNewlineCharacterSet()).stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!)!
let request: NSURLRequest = NSURLRequest(URL: imgURL)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler: {(response: NSURLResponse!,data: NSData!,error: NSError!) -> Void in
var fileCacheName = String(format: "%04d", index)
dispatch_async(dispatch_get_main_queue(), {
var fileExt = (data != nil && error == nil) ? Utility.checkImageType(data) : ""
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as! String
let imagePath = paths.stringByAppendingPathComponent("\(fileCacheName).png")
if data.writeToFile(imagePath, atomically: false)
{
println("saved")
}
if index < filesPath.count - 1
{
var nextIndex:Int = index + 1
self.downloadFiles(nextIndex, filesPath: filesPath)
}
})
})
}
}
Answer
Based on the comment below, I found this thread : objective c - Proper use of beginBackgroundTaskWithExpirationHandler . I can solve my problem with it.
For downloading and storing of the images, instead of writing the logic yourself, I suggest you use some well known libraries, like:
https://github.com/Haneke/Haneke
https://github.com/rs/SDWebImage
Reason behind that is those libraries are well tested, quite robust and mainly very easy to use for basic tasks.
Now for the background download, there is beginBackgroundTaskWithExpirationHandler: that is specifically designed to do that. When you use it, you will get few more minutes to execute whatever you need (after that limit, your application will get terminated no matter what).
You can write following methods:
func beginBackgroundTask() -> UIBackgroundTaskIdentifier {
return UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({})
}
func endBackgroundTask(taskID: UIBackgroundTaskIdentifier) {
UIApplication.sharedApplication().endBackgroundTask(taskID)
}
When you want to use it, you just simple begin / end the task when starting / finishing the download call:
// Start task
let task = self.beginBackgroundTask()
// Do whatever you need
self.someBackgroundTask()
// End task
self.endBackgroundTask(task)
Hope it helps!
Use beginBackgroundTaskWithExpirationHandler: from the UIApplication to start a background task when the app enters the background
See Apple's document on multitasking background execution for details. See download in background in iphone its a similar question.
I use the setObjectForKey method when I try to save a custom object, but when I try to retrieve it for the same key, it returns nil. I have been researching and I know that there is some issue with set custom objects to NSUserDefaults so I tried using NSKeyedArchiver class, but it still returns nil.
//
// VirtualRewardsClient.swift
// VirtualRewardsNew
//
// Created by Dhruv Mangtani on 3/14/15.
// Copyright (c) 2015 dhruv.mangtani. All rights reserved.
//
import UIKit
class VirtualRewardsClient{
class var sharedInstance: VirtualRewardsClient{
struct Static{
static var instance = VirtualRewardsClient()
}
return Static.instance
}
func getClass() -> Class{
var defaults = NSUserDefaults.standardUserDefaults()
println(defaults.objectForKey(classKey))
if let data = defaults.objectForKey(classKey) as? NSData{
//let unarc = NSKeyedUnarchiver(forReadingWithData: data)
//unarc.setClass(Class.self, forClassName: "Class")
let currentClass = NSKeyedUnarchiver.unarchiveObjectWithData(data) as Class
println("entering")
Class.sharedInstance.students = currentClass.students
Class.sharedInstance.teacher = currentClass.teacher
return currentClass
} else {
var newClass = Class()
newClass.students = [Student]()
var encodedObject: NSData = NSKeyedArchiver.archivedDataWithRootObject(newClass)
defaults.setObject(encodedObject, forKey: classKey)
defaults.synchronize()
return newClass
}
}
}
Class.swift
import Foundation
import UIKit
let classKey = "CLASS_KEY"
class Class: NSObject{
let defaults = NSUserDefaults.standardUserDefaults()
class var sharedInstance: Class{
struct Static{
static var instance: Class = VirtualRewardsClient.sharedInstance.getClass()
}
return Static.instance
}
var students:[Student] = [Student]()
var teacher = Teacher(currentClass: sharedInstance)
func addStudent(name: String, value: Int){
students.append(Student(name: name, startingPoints: value))
defaults.setObject(Class.sharedInstance, forKey: classKey)
VirtualRewardsClient.sharedInstance.getClass()
}
func addStudent(name: String){
students.append(Student(name: name))
defaults.setObject(Class.sharedInstance, forKey: classKey)
VirtualRewardsClient.sharedInstance.getClass()
}
func printClass(){
for i in students{
println("Student: \(i.name), Points: \(i.points)")
}
}
}
App Delegate:
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
/*var storyboard = UIStoryboard(name: "Main", bundle: nil)
var vc = storyboard.instantiateViewControllerWithIdentifier("StudentsNavigationController") as UIViewController
window?.rootViewController = vc
println("didFinishLaunchingWithOptions\(window?.rootViewController)")
*/
return true
}
func applicationWillResignActive(application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
func applicationDidEnterBackground(application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
println("applicationDidEnterBackground")
}
func applicationWillEnterForeground(application: UIApplication) {
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
println("applicationWillEnterForeground")
}
func applicationDidBecomeActive(application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
println("applicationDidBecomeActive")
}
func applicationWillTerminate(application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
}
change Class to VirtualRewardsClient