change UITabBar.appearance().tintColor at runtime - swift

I want to allow the user to change the whole App-Color.
But whenever I select a color it is only changed after the App will be restarted.
Is there any option to change the color at runtime?
I've set up a Button with a function like this to create a ColorPicker
#IBAction func colorChange(_ sender: UIButton) {
// Initializing Color Picker
let picker = UIColorPickerViewController()
// Setting the Initial Color of the Picker
picker.selectedColor = UIColor(named: "MyGreen")!
// Setting Delegate
picker.delegate = self
// Presenting the Color Picker
self.present(picker, animated: true, completion: nil)
}
And when the user picked a color, I make the changes in this function
func colorPickerViewControllerDidFinish(_ viewController: UIColorPickerViewController) {
defaults.set(viewController.selectedColor, forKey: "myColor")
UITabBar.appearance().tintColor = viewController.selectedColor
}
To change the color at startup I've implemented this in AppDelegate
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
var myColor = defaults.color(forKey: "myColor")
if myColor == nil {
myColor = UIColor(named: "MyGreen")
}
UITabBar.appearance().tintColor = myColor
return true
}

Someone gave me an Article to read how it can be done.
https://zamzam.io/protocol-oriented-themes-for-ios-apps/
I've ended up doing it like this:
func colorPickerViewControllerDidFinish(_ viewController: UIColorPickerViewController) {
defaults.set(viewController.selectedColor, forKey: "myColor")
apply(for: UIApplication.shared, color: viewController.selectedColor)
}
by adding this function
func apply(for application: UIApplication, color: UIColor) {
UITabBar.appearance().tintColor = color
application.windows.reload()
}
and these extensions
public extension UIWindow {
/// Unload all views and add back.
/// Useful for applying `UIAppearance` changes to existing views.
func reload() {
subviews.forEach { view in
view.removeFromSuperview()
addSubview(view)
}
}
}
public extension Array where Element == UIWindow {
/// Unload all views for each `UIWindow` and add back.
/// Useful for applying `UIAppearance` changes to existing views.
func reload() {
forEach { $0.reload() }
}
}
If it's good or not I don't know - but it worked for me.

have you try using UITabBar.appearance().barTintColor ?

Related

Integrating Unity+Vuforia to existing iOS project make user interaction not working

Currently I'm following this tutorial to integrate Unity+Vuforia project to my existing iOS project. I manage to be able to show the Unity view inside my ARViewController. The thing is I lost all the user interaction in my view controller: my touch event for back button not fire up.
import Foundation
class ARViewController: UIViewController {
var unityView: UIView?
static func instantiateViewController() -> ARViewController {
let controller = UIStoryboard.main.instantiateViewController(withIdentifier: "ARViewController") as! ARViewController
return controller
}
override func viewDidLoad() {
super.viewDidLoad()
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
appDelegate.currentUnityController = UnityAppController()
appDelegate.currentUnityController?.application(UIApplication.shared, didFinishLaunchingWithOptions: nil)
appDelegate.startUnity()
NotificationCenter.default.addObserver(self, selector: #selector(handleUnityReady), name: NSNotification.Name("UnityReady"), object: nil)
}
}
#IBAction func onBackPressed(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
appDelegate.stopUnity()
}
}
#objc func backPressedTest() {
}
#objc func handleUnityReady() {
showUnitySubView()
}
func showUnitySubView() {
guard let unityView = UnityGetGLView() else { return }
self.unityView = unityView
// insert subview at index 0 ensures unity view is behind current UI view
view?.addSubview(unityView)
unityView.translatesAutoresizingMaskIntoConstraints = false
let views = ["view": unityView]
let w = NSLayoutConstraint.constraints(withVisualFormat: "|-0-[view]-0-|", options: [], metrics: nil, views: views)
let h = NSLayoutConstraint.constraints(withVisualFormat: "V:|-50-[view]-0-|", options: [], metrics: nil, views: views)
view.addConstraints(w + h)
let button = UIButton(type: .custom)
button.setImage(#imageLiteral(resourceName: "ic_back_black").withRenderingMode(.alwaysTemplate), for: .normal)
button.addTarget(self, action:#selector(backPressed), for: .touchUpInside)
button.frame = CGRect(x: 0, y: 0, width: 28, height: 60)
button.widthAnchor.constraint(equalToConstant: button.frame.width).isActive = true
button.tintColor = UIColor.purpleBrown()
view?.addSubview(button)
}
}
I also notice that button from Unity also have any effect when I touch it. The back button inside the green bar is from Unity. And the blue button is from my ARViewController. Both seem don't reach to touch event.
Debug elements:
It happens when I put the Unity configuration at the top of application:didFinishLaunchingWithOptions:, above my existing configuration for another services that I use in the project. For someone who encounter this problem in the future, here's my appDelegate.swift:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
self.application = application
// Another existing settings for the project, e.g. fabric
Fabric.with([Crashlytics.self])
// Put the Unity configuration at the bottom of the function
unity_init(CommandLine.argc, CommandLine.unsafeArgv)
currentUnityController = UnityAppController()
currentUnityController?.application(application, didFinishLaunchingWithOptions: launchOptions)
startUnity()
stopUnity()
return true
}
For the viewWillAppear(_:) in the view controller:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
setupBackBarButtonItems(back: true, isDark: true)
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
appDelegate.startUnity()
showUnitySubView()
}
}
As mentioned by #Prashant in the comment, the UnityReady notification only get called once. So I don't use it.
Then I just call stopUnity() in viewWillDisappear(_:):
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
appDelegate.stopUnity()
}
}
The current problem is I can't kill the unity process if I leave the screen. It's known bug and I'm still figuring out how to do that, if it's possible.
I had the same issue but moving Unity configuration to the end of applicationDidFinishLaunchingWithOption method didn't solve it and I still had a UIWindow in front of my screen that steals all user interactions.
My solution is not to create a new window in UnityAppController.mm, but using the current application keyWindow.
Replace:
_window = [[UIWindow alloc] initWithFrame: [UIScreen mainScreen].bounds];
with:
_window = [UIApplication sharedApplication].keyWindow;

iOS firebase: FIRAuthUIDelegate.authUI not being called

I am trying to launch google login from AppDelegate.swift and then launch my app's main screen upon login success.
I am able to
show the google login button as shown above
the user is sent to google to sign in
the user is sent back to original (step 1)
After step 3. I'd like to send the user to my app's main page.
My code is below. The problem I'm having is that authUI is not being called.
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, FIRAuthUIDelegate {
var window: UIWindow?
var authUI: FIRAuthUI?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
FIRApp.configure()
authUI = FIRAuthUI.defaultAuthUI()
authUI?.delegate = self
let providers: [FIRAuthProviderUI] = [FIRGoogleAuthUI()]
authUI?.providers = providers
// show google login button
let authViewController = authUI?.authViewController()
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
self.window?.rootViewController = authViewController
self.window?.makeKeyAndVisible()
return true
}
func application(application: UIApplication, openURL url: NSURL, options: [String: AnyObject]) -> Bool {
return GIDSignIn.sharedInstance().handleURL(url, sourceApplication: options[UIApplicationOpenURLOptionsSourceApplicationKey] as? String, annotation: options[UIApplicationOpenURLOptionsAnnotationKey])
}
func authUI(authUI: FIRAuthUI, didSignInWithUser user: FIRUser?, error: NSError?) {
// launch main view controller
}
}
EDIT: This appears to be a duplicate of another question. The other question's title is quite general and only gets to the details a few lines deep. In any case, I believe Chris's answer is more thorough than the one there. I think both the question and answers here are clearer, more pointed and more thorough so it would be a mistake to just direct people here to go there as would happen if this was marked as a duplicate.
I think your problem lies here, in the - (void)signInWithProviderUI:(id<FIRAuthProviderUI>)providerUI method.
The delegate method is called in the dismissViewControllerAnimated:completion: completion block.
[self.navigationController dismissViewControllerAnimated:YES completion:^{
[self.authUI invokeResultCallbackWithUser:user error:error];
}];
As you can see from the Apple docs, this method is expected to be called on a modally presented viewController. You are displaying it as a root view controller. Try displaying it with a modal from a UIViewController, and things should work out. To debug this try and set a breakpoint at line 193 to see that it won't get hit. I would be very surprised if this doesn't work when you display the authController modally.
To come up with a possible solution to your problem (I am assuming you want to ensure a user is signed in before using your app). The below is a simplification of what I am using in an app currently.
EDIT: Updated for the new 1.0.0 FirebaseUI syntax.
class MainTabController: UITabBarController, FIRAuthUIDelegate {
let authUI: FUIAuth? = FUIAuth.defaultAuthUI()
override func viewDidLoad() {
super.viewDidLoad()
var authProviders = [FUIFacebookAuth(), FUIGoogleAuth()]
authUI.delegate = self
authUI.providers = authProviders
//I use this method for signing out when I'm developing
//try! FIRAuth.auth()?.signOut()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if !isUserSignedIn() {
showLoginView()
}
}
private func isUserSignedIn() -> Bool {
guard FIRAuth.auth()?.currentUser != nil else { return false }
return true
}
private func showLoginView() {
if let authVC = FUIAuth.defaultAuthUI()?.authViewController() {
present(authVC, animated: true, completion: nil)
}
}
func authUI(_ authUI: FUIAuth, didSignInWith user: FIRUser?, error: Error?) {
guard let user = user else {
print(error)
return
}
...
}
It must be a problem of reference.
class AppDelegate: UIResponder, UIApplicationDelegate, FIRAuthUIDelegate {
var window: UIWindow?
let authUI = FIRAuthUI.defaultAuthUI()
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
FIRApp.configure()
authUI.delegate = self
let providers: [FIRAuthProviderUI] = [FIRGoogleAuthUI()]
authUI.providers = providers
// show google login button
let authViewController = authUI.authViewController()
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
self.window?.rootViewController = authViewController
self.window?.makeKeyAndVisible()
return true
}
}
Try this. AppDelegate will hold the reference of authUI and its delegate.

Detect and Remove blank tableViewCell in Swift

I have two tableView running in my project.I have successfully passed data between two tableView and saved it as a tableView array into my second tableView using segue.I have another function to connect my second tableview using tabBar button. Everytime, when I press the tabBar button it creates a blank tableViewCell at indexPath:0. I can understand why is happening but I am not sure how to stop. My code below
AppDelegate:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
let defaultValues = ["NewArray": [String]()]
UserDefaults.standard.register(defaults: defaultValues)
return true
}
MY Second VC
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let defaults = UserDefaults.standard
newArray = defaults.array(forKey: "NewArray") as! [String]
// newArray.append(newItem)
newArray.insert(newItem, at: 0)
defaults.set(newArray, forKey: "NewArray")
self.favTableView?.reloadData()
}
Thanks in Advance...
Actually the easiest solution is to check if newItem is not empty, because if the view is presented from the segue a (non-empty) string is passed to the newItem property.
override func viewDidLoad() {
super.viewDidLoad()
let defaults = UserDefaults.standard
newArray = defaults.array(forKey: "NewArray") as! [String]
// newArray.append(newItem)
if !newItem.isEmpty {
newArray.insert(newItem, at: 0)
defaults.set(newArray, forKey: "NewArray")
}
self.favTableView?.reloadData()
}

3D Touch Quick Actions not working properly with SpriteKit

I'm currently developing a game using Swift 3, SpriteKit, and Xcode 8 beta. I'm trying to implement static 3D Touch Quick Actions from the home screen through the info.plist. Currently, the actions appear fine from the home screen, but don't go to the right SKScene - goes to the initial scene or last opened scene (if app is still open) which means that the scene is not being changed. I've tried various ways of setting the scene inside the switch statement, but none seem to work properly for presenting an SKScene as the line window!.rootViewController?.present(gameViewController, animated: true, completion: nil) only works on UIViewController.
Various parts of this code are from various tutorials, but I'm fairly sure through my investigation that the broken part is the scene being presented (unless I'm wrong), because it shouldn't even load any scene if a part is broken.
Are there any ways I can present an SKScene from the AppDelegate or set the opening scene based of the switch statement?
AppDelegate
import UIKit
import SpriteKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
guard !handledShortcutItemPress(forLaunchOptions: launchOptions) else { return false } // ADD THIS LINE
return true
}
}
extension AppDelegate: ShortcutItem {
/// Perform action for shortcut item. This gets called when app is active
func application(_ application: UIApplication, performActionForShortcutItem shortcutItem: UIApplicationShortcutItem, completionHandler: (Bool) -> Void) {
completionHandler(handledShortcutItemPress(forItem: shortcutItem))
}
}
extension AppDelegate: ShortcutItemDelegate {
func shortcutItem1Pressed() {
Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(loadShopScene), userInfo: nil, repeats: false)
}
#objc private func loadShopScene() {
let scene = ShopScene(size: CGSize(width: 768, height: 1024))
loadScene(scene: scene, view: window?.rootViewController?.view)
}
func shortcutItem2Pressed() {
Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(loadGameScene), userInfo: nil, repeats: false)
}
#objc private func loadGameScene() {
let scene = GameScene(size: CGSize(width: 768, height: 1024))
loadScene(scene: scene, view: window?.rootViewController?.view)
}
func shortcutItem3Pressed() {
// do something else
}
func shortcutItem4Pressed() {
// do something else
}
func loadScene(scene: SKScene?, view: UIView?, scaleMode: SKSceneScaleMode = .aspectFill) {
guard let scene = scene else { return }
guard let skView = view as? SKView else { return }
skView.ignoresSiblingOrder = true
#if os(iOS)
skView.isMultipleTouchEnabled = true
#endif
scene.scaleMode = scaleMode
skView.presentScene(scene)
}
}
3DTouchQuickActions.swift
import Foundation
import UIKit
/// Shortcut item delegate
protocol ShortcutItemDelegate: class {
func shortcutItem1Pressed()
func shortcutItem2Pressed()
func shortcutItem3Pressed()
func shortcutItem4Pressed()
}
/// Shortcut item identifier
enum ShortcutItemIdentifier: String {
case first // I use swift 3 small letters so you have to change your spelling in the info.plist
case second
case third
case fourth
init?(fullType: String) {
guard let last = fullType.components(separatedBy: ".").last else { return nil }
self.init(rawValue: last)
}
public var type: String {
return Bundle.main.bundleIdentifier! + ".\(self.rawValue)"
}
}
/// Shortcut item protocol
protocol ShortcutItem { }
extension ShortcutItem {
// MARK: - Properties
/// Delegate
private weak var delegate: ShortcutItemDelegate? {
return self as? ShortcutItemDelegate
}
// MARK: - Methods
/// Handled shortcut item press first app launch (needed to avoid double presses on first launch)
/// Call this in app Delegate did launch with options and exit early (return false) in app delegate if this method returns true
///
/// - parameter forLaunchOptions: The [NSObject: AnyObject]? launch options to pass in
/// - returns: Bool
func handledShortcutItemPress(forLaunchOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
guard let launchOptions = launchOptions, let shortcutItem = launchOptions[UIApplicationLaunchOptionsKey.shortcutItem] as? UIApplicationShortcutItem else { return false }
handledShortcutItemPress(forItem: shortcutItem)
return true
}
/// Handle shortcut item press
/// Call this in the completion handler in AppDelegate perform action for shortcut item method
///
/// - parameter forItem: The UIApplicationShortcutItem the press was handled for.
/// - returns: Bool
func handledShortcutItemPress(forItem shortcutItem: UIApplicationShortcutItem) -> Bool {
guard let _ = ShortcutItemIdentifier(fullType: shortcutItem.type) else { return false }
guard let shortcutType = shortcutItem.type as String? else { return false }
switch shortcutType {
case ShortcutItemIdentifier.first.type:
delegate?.shortcutItem1Pressed()
case ShortcutItemIdentifier.second.type:
delegate?.shortcutItem2Pressed()
case ShortcutItemIdentifier.third.type:
delegate?.shortcutItem3Pressed()
case ShortcutItemIdentifier.fourth.type:
delegate?.shortcutItem4Pressed()
default:
return false
}
return true
}
}
Your code is not working because in your app delegate you create a new instance of GameViewController instead of referencing the current one
let gameViewController = GameViewController() // creates new instance
I am doing exactly what you are trying to do with 3d touch quick actions in 2 of my games. I directly load the scene from the appDelegate, dont try to change the gameViewController scene for this.
I use a reusable helper for this. Assuming you set up everything correctly in your info.plist. (I use small letters in the enum so end your items with .first, .second etc in the info.plist), remove all your app delegate code you had previously for the 3d touch quick actions.
Than create a new .swift file in your project and add this code
This is swift 3 code.
import UIKit
/// Shortcut item delegate
protocol ShortcutItemDelegate: class {
func shortcutItemDidPress(_ identifier: ShortcutItemIdentifier)
}
/// Shortcut item identifier
enum ShortcutItemIdentifier: String {
case first // I use swift 3 small letters so you have to change your spelling in the info.plist
case second
case third
case fourth
private init?(fullType: String) {
guard let last = fullType.componentsSeparatedByString(".").last else { return nil }
self.init(rawValue: last)
}
public var type: String {
return (Bundle.main.bundleIdentifier ?? "NoBundleIDFound") + ".\(rawValue)"
}
}
/// Shortcut item protocol
protocol ShortcutItem { }
extension ShortcutItem {
// MARK: - Properties
/// Delegate
private weak var delegate: ShortcutItemDelegate? {
return self as? ShortcutItemDelegate
}
// MARK: - Methods
func didPressShortcutItem(withOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
guard let shortcutItem = launchOptions?[.shortcutItem] as? UIApplicationShortcutItem else { return false }
didPressShortcutItem(shortcutItem)
return true
}
/// Handle item press
#discardableResult
func didPressShortcutItem(_ shortcutItem: UIApplicationShortcutItem) -> Bool {
guard let _ = ShortcutItemIdentifier(fullType: shortcutItem.type) else { return false }
switch shortcutItem.type {
case ShortcutItemIdentifier.first.type:
delegate?.shortcutItemDidPress(.first)
case ShortcutItemIdentifier.second.type:
delegate?.shortcutItemDidPress(.second)
case ShortcutItemIdentifier.third.type:
delegate?.shortcutItemDidPress(.third)
case ShortcutItemIdentifier.fourth.type:
delegate?.shortcutItemDidPress(.fourth)
default:
return false
}
return true
}
}
Than in your app delegate create an extension with this method (you missed this in your code)
extension AppDelegate: ShortcutItem {
/// Perform action for shortcut item. This gets called when app is active
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: (Bool) -> Void) {
completionHandler(didPressShortcutItem(shortcutItem))
}
Than you need to adjust the didFinishLaunchingWithOptions method in your AppDelegate to look like this
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
...
return !didPressShortcutItem(withOptions: launchOptions)
}
And than finally create another extension confirming to the ShortcutItem delegate
extension AppDelegate: ShortcutItemDelegate {
func shortcutItemDidPress(_ identifier: ShortcutItemIdentifier) {
switch identifier {
case .first:
let scene = GameScene(size: CGSize(width: 1024, height: 768))
loadScene(scene, view: window?.rootViewController?.view)
case .second:
//
case .third:
//
case .fourth:
//
}
}
func loadScene(scene: SKScene?, view: UIView?, scaleMode: SKSceneScaleMode = .aspectFill) {
guard let scene = scene else { return }
guard let skView = view as? SKView else { return }
skView.ignoresSiblingOrder = true
#if os(iOS)
skView.isMultipleTouchEnabled = true
#endif
scene.scaleMode = scaleMode
skView.presentScene(scene)
}
}
The load scene method I normally have in another helper which is why I pass the view into the func.
Hope this helps.

how to use 'presentViewController' with cover View and main page

A lots app, they have the cover view, like facebook, wechat...
but when I try to use presentViewController let the cover present to main page, that still in the cover vie. The following is my code:
var window: UIWindow?
var coverVC:CoverView?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
window = UIWindow(frame: UIScreen.mainScreen().bounds)
window?.backgroundColor = UIColor.lightGrayColor()
coverVC = ViewController()
coverVC?.refresh(window!.frame)
window!.makeKeyAndVisible()
return true
}
Coverview
class CoverView: UIViewController {
func refresh(frame:CGRect){
self.view.frame = frame
self.view.backgroundColor = UIColor.whiteColor()
var mainVC:mainView = mainView()
mainVC.refresh(frame)
self.dismissViewControllerAnimated(false, completion: nil)
self.presentViewController(mainVC, animated: false, completion: nil)
}
}
Main view:
class mainView: UIViewController {
func refresh(frame:CGRect){
self.view.frame = frame
self.view.backgroundColor = UIColor.blueColor()
}
}
Maybe can use other easy way to achieve the goal, that's ok.
otherwise, please don't use "UInavigationcontroller", I know that can present view, but I don't want the bar in cover view, thanks.
I find the correct with cover/login view, it is called SplashScreen.
And in xcode 9, you can find at [TARGETS]>[APP icons and Launch Images] (Image).(The past version, you need add "launch image" in plist file.)
Otherwise, If you doesn't want use this view, just delete "LaunchScreen".