Xcode allows to create launch screen in .xib files via Interface Builder. Is it possible to execute some code with the xib, just like in usual view controllers? It would be great if we can set different text/images/etc while app launching.
No, it's not possible.
When launch screen is being displayed your app will be in loading state.
Even the - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions will not be completely executed while the launch screen is displayed.
So it's clear that, you don't have any access to your app and so at this point you can't execute any code.
I was trying to do the same thing here. :)
I really liked some of the apps, in which they do a little dynamic greeting text and image each time the app is launched, such as "You look good today!", "Today is Friday, a wonderful day", etc, which is very cute.
I did some search, below is how to do it:
(My code is XCode 7, with a launchscreen.xib file)
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var customizedLaunchScreenView: UIView?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
application.statusBarHidden = true
// customized launch screen
if let window = self.window {
self.customizedLaunchScreenView = UIView(frame: window.bounds)
self.customizedLaunchScreenView?.backgroundColor = UIColor.greenColor()
self.window?.makeKeyAndVisible()
self.window?.addSubview(self.customizedLaunchScreenView!)
self.window?.bringSubviewToFront(self.customizedLaunchScreenView!)
UIView.animateWithDuration(1, delay: 2, options: .CurveEaseOut,
animations: { () -> Void in
self.customizedLaunchScreenView?.alpha = 0 },
completion: { _ in
self.customizedLaunchScreenView?.removeFromSuperview() })
}
return true
}
// other stuff ...
}
Just do what ever you wanted to show, text, images, animations, etc. inside the customizedLaunchScreenView here.
At the end of the launching, just fade out this customized UIView using alpha value change, then remove it completely.
How cool is that? I absolutely love it!
Hope it helps.
I was also trying to achieve this. I tried the following, it gives a delay of couple of seconds but works for me.
Create and set a launch screen in project settings.
Create a view controller with custom class (SplashViewController) and set it your starting view controller in storyboard.
Add a container view in it and set it to full screen.
Set embedded segue to a Storyboard Reference.
Select Storyboard Reference and set launch screen in StoryBoard property from Attribute inspector.
Do whatever you want in SplashViewController (play animation or session check etc) and perform segue when done.
Hope it helps!
Language: Swift 4
Hello, here's a solution for using xib for launch screen.
Adding new class named LaunchView to your project, then u'll have a new file LaunchView.swift in project.
Go to LauchImage.xib to set class to LaunchView (which it's your new class).
Adding some code, & reading some information show at the LaunchView.swift. Here's is my code for displaying version of app at launch screen.
class LaunchView: UIView {
lazy var versionLabel: UILabel = {
let label = Factory.label(style: LabelStyle.custom(font: .regular, size: 10, color: .other(ColorUtility.versionGray)), mutiLine: false, textAlignment: .center)
label.text = "ver \(Constants.appVersion)"
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
nibSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
nibSetup()
}
private func nibSetup() {
addSubview(versionLabel)
versionLabel.snp.makeConstraints { (make) in
make.bottom.equalTo(safeAreaLayoutGuide.snp.bottom).offset(-6)
make.centerX.equalToSuperview()
}
}
}
Related
I am writing a logic puzzle game in SpriteKit that plays on the iPad and is against the clock, and am struggling to hide the puzzle neatly when the app goes into the background.
The issue is that user shouldn't be able to double-tap on the home button and see the full puzzle in the App Switcher, as this would allow them to work through it without the clock running.
This is the solution I have come up with:
In the Singleton GameManager there is a variable that is a SKTexture(), to hold a screenshot texture, and in my AppDelegate I have:
func applicationWillResignActive(_ application: UIApplication) {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "resigning"), object: self)
let tempBackground = UIImageView(image: UIImage(cgImage: GameManager.shared.puzzleImage.cgImage()))
tempBackground.frame = CGRect(x: 0, y: 0, width: 1024, height: 768)
tempBackground.tag = 1000
self.window?.addSubview(tempBackground)
self.window?.bringSubviewToFront(tempBackground)
}
This sends out a notification when it's about to resign the focus, which is picked up by my GameScene, which then creates a screenshot and stores it to the variable in my GameManager. This is then added as a subview in the AppDelegate.
class GameScene: SKScene {
override func didMove(to view: SKView) {
// Layout puzzle here
NotificationCenter.default.addObserver(self, selector: #selector(appToBackground), name: NSNotification.Name(rawValue: "resigning") , object: nil)
}
#objc private func appToBackground() {
// Save state of puzzle and hide it
GameManager.shared.puzzleImage = SKView().texture(from: self)!
}
}
This all works. But not brilliantly. There is a noticeable time-lag between double-tapping and the tempBackground being added - the App Switcher shows the puzzle in detail very briefly and then changes the image to the hidden puzzle.
When the app comes back into focus, the following is called in AppDelegate:
func applicationDidBecomeActive(_ application: UIApplication) {
if let tempBackground = self.window?.viewWithTag(1000) {
tempBackground.removeFromSuperview()
}
}
But when the app returns to focus, the tempBackground is shown, then there is a very brief glimpse of the puzzle in all its detail, before once again showing the hidden puzzle.
I may well have gone about this completely the wrong way, but after reading various archive questions and articles on the internet, this seemed to be way to go.
What I'd like to know is: is there any way that I can have the hidden puzzle shown in the App Switcher immediately and avoid the flash of puzzle detail when returning to the puzzle?
Thanks.
There is no issue in adding subview logic, but the issue is in where are you triggering it, as per apple docs, you should add subview in applicationDidEnterBackground and should remove you subview and prepare your app to display in applicationWillEnterForeground
As I have created Xcode project in Xcode 12, my project has scene delegate, here is the code I used and O/P is shown in gif below
func sceneWillResignActive(_ scene: UIScene) {
guard let view = Bundle.main.loadNibNamed("HiddenView", owner: nil, options: [:])?[0] as? HiddenView else { return }
view.tag = 1000
view.frame = UIApplication.shared.windows[0].frame
UIApplication.shared.windows[0].addSubview(view)
UIApplication.shared.windows[0].bringSubviewToFront(view)
}
func sceneWillEnterForeground(_ scene: UIScene) {
if let tempBackground = UIApplication.shared.windows[0].viewWithTag(1000) {
tempBackground.removeFromSuperview()
}
}
EDIT 1:
As OP has mentioned, that he is not using Scene Delegate I am updating the answer for AppDelegate
func applicationWillResignActive(_ application: UIApplication) {
guard let view = Bundle.main.loadNibNamed("HiddenView", owner: nil, options: [:])?[0] as? HiddenView else { return }
view.tag = 1000
view.frame = UIApplication.shared.windows[0].frame
UIApplication.shared.windows[0].addSubview(view)
UIApplication.shared.windows[0].bringSubviewToFront(view)
}
func applicationDidBecomeActive(_ application: UIApplication) {
if let tempBackground = UIApplication.shared.windows[0].viewWithTag(1000) {
tempBackground.removeFromSuperview()
}
}
I have a solution, although it feels more like a work-around than a proper solution.
The problem is that iOS takes a snapshot of the screen before sending the app into the background and uses this when the app returns to the foreground.
This snapshot can be replaced by using either a predefined view (see #Sandeep's solution) or creating a new snapshot of the screen once the puzzle has been hidden (my original attempted solution).
However, removing this dummy in the applicationDidBecomeActive means that the original snapshot taken by the iOS is shown briefly before updating the screen.
There is a function UIApplication.shared.ignoreSnapshotOnNextApplicationLaunch() which can be entered into the applicationWillResignActive() function, but it doesn't seem to be working all the time - just most of it. In fact, on the Apple Developer's forum there's a comment about how it's inconsistent and a bug was reported. But that was over a year ago and it's still inconsistent.
Instead, in this function add the dummy not to the self.window? directly, but to the view of the rootViewController, using
self.window?.rootViewController!.view.addSubview(dummyImage)
Then don't actually remove this when the app comes back into the foreground. Instead, use Notification to alert GameScene that the app is back in the foreground. This can then run a method which adds a UITapGestureRecognizer to the scene. This in turn can be linked to a method that fades out the dummy image, before removing both it and the UITapGestureRecognizer once it's completed.
Perhaps not the most elegant solution, but it seems to work.
I want to be able to add a background image to my ios app that applies for all views and not having to add this image to every single view. Is it possible to add an image to UIWindow or something?
Now, when I do a transition, the background of the new view also "transitions" over my previous view even though its the same background. I want to be able to only se the content transition, and not the background, which is the same.
You should be able to achieve this by:
subclassing UIWindow and overriding drawRect to draw your image; details about subclassing UIWindow here
and then by using UIAppearance to control the background colour for the rest of the views in your app (you should set it to clearColor); details about this here, though I think you'll need to subclass the views used by your controllers in order to cope with the note in the accepted answer
A basic implementation could look like this:
class WallpaperWindow: UIWindow {
var wallpaper: UIImage? = UIImage(named: "wallpaper") {
didSet {
// refresh if the image changed
setNeedsDisplay()
}
}
init() {
super.init(frame: UIScreen.mainScreen().bounds)
//clear the background color of all table views, so we can see the background
UITableView.appearance().backgroundColor = UIColor.clearColor()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func drawRect(rect: CGRect) {
// draw the wallper if set, otherwise default behaviour
if let wallpaper = wallpaper {
wallpaper.drawInRect(self.bounds);
} else {
super.drawRect(rect)
}
}
}
And in AppDelegate:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow? = WallpaperWindow()
TL;DR
Need to keep autorotation, but exclude one UIView from autorotating on orientation change, how?
Back story
I need to keep a UIView stationary during the animation accompanied by autorotation (which happens on orientation change). Similar to how the iOS camera app handles the rotation (i.e controls rotate in their place).
Things I've tried
Returning false from shouldAutorotate(), subscribing to UIDeviceOrientationDidChangeNotification, and trying to manually handle the rotation event for each view separately.
Works well if you don't need to change any of your UIViews' places, otherwise it's a pain figuring out where it should end up and how to get it there
Placing a non rotating UIWindow under the main UIWindow, and setting the main UIWindow background colour to clear.
This works well if it's only one item, but I don't want to manage a bunch of UIWindows
Inverse rotation I.e rotating the UIView in the opposite direction to the rotation. Not reliable, and looks weird, it's also vertigo inducing
Overriding the animation in the viewWillTransitionToSize method. Failed
And a bunch of other things that would be difficult to list here, but they all failed.
Question
Can this be done? if so, how?
I'm supporting iOS8+
Update This is how the views should layout/orient given #Casey's example:
I have faced with same problem and found example from Apple, which helps to prevent UIView from rotation: https://developer.apple.com/library/ios/qa/qa1890/_index.html
However, if UIView is not placed in the center of the screen, you should handle new position manually.
i think part of the reason this is so hard to answer is because in practice it doesn't really make sense.
say i make a view that uses autolayout to look like this in portrait and landscape:
if you wanted to prevent c from rotating like you are asking, what would you expect the final view to look like? would it be one of these 3 options?
without graphics of the portrait/landscape view you are trying to achieve and a description of the animation you are hoping for it'll be very hard to answer your question.
are you using NSLayoutConstraint, storyboard or frame based math to layout your views? any code you can provide would be great too
If you're wanting to have the same effect as the camera app, use size classes (see here and here).
If not, what is wrong with creating a UIWindow containing a view controller that doesn't rotate? The following code seems to work for me (where the UILabel represents the view you don't want to rotate).
class ViewController: UIViewController {
var staticWindow: UIWindow!
override func viewDidLoad() {
super.viewDidLoad()
showWindow()
}
func showWindow() {
let frame = CGRect(x: 10, y: 10, width: 100, height: 100)
let vc = MyViewController()
let label = UILabel(frame: frame)
label.text = "Hi there"
vc.view.addSubview(label)
staticWindow = UIWindow(frame: frame)
staticWindow.rootViewController = MyViewController()
staticWindow.windowLevel = UIWindowLevelAlert + 1;
staticWindow.makeKeyAndVisible()
staticWindow.rootViewController?.presentViewController(vc, animated: false, completion: nil)
}
}
class MyViewController: UIViewController {
override func shouldAutorotate() -> Bool {
return false
}
override func shouldAutomaticallyForwardRotationMethods() -> Bool {
return false
}
override func shouldAutomaticallyForwardAppearanceMethods() -> Bool {
return false
}
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.Portrait
}
}
I want to be able to add a background image to my ios app that applies for all views and not having to add this image to every single view. Is it possible to add an image to UIWindow or something?
Now, when I do a transition, the background of the new view also "transitions" over my previous view even though its the same background. I want to be able to only se the content transition, and not the background, which is the same.
You should be able to achieve this by:
subclassing UIWindow and overriding drawRect to draw your image; details about subclassing UIWindow here
and then by using UIAppearance to control the background colour for the rest of the views in your app (you should set it to clearColor); details about this here, though I think you'll need to subclass the views used by your controllers in order to cope with the note in the accepted answer
A basic implementation could look like this:
class WallpaperWindow: UIWindow {
var wallpaper: UIImage? = UIImage(named: "wallpaper") {
didSet {
// refresh if the image changed
setNeedsDisplay()
}
}
init() {
super.init(frame: UIScreen.mainScreen().bounds)
//clear the background color of all table views, so we can see the background
UITableView.appearance().backgroundColor = UIColor.clearColor()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func drawRect(rect: CGRect) {
// draw the wallper if set, otherwise default behaviour
if let wallpaper = wallpaper {
wallpaper.drawInRect(self.bounds);
} else {
super.drawRect(rect)
}
}
}
And in AppDelegate:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow? = WallpaperWindow()
Xcode allows to create launch screen in .xib files via Interface Builder. Is it possible to execute some code with the xib, just like in usual view controllers? It would be great if we can set different text/images/etc while app launching.
No, it's not possible.
When launch screen is being displayed your app will be in loading state.
Even the - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions will not be completely executed while the launch screen is displayed.
So it's clear that, you don't have any access to your app and so at this point you can't execute any code.
I was trying to do the same thing here. :)
I really liked some of the apps, in which they do a little dynamic greeting text and image each time the app is launched, such as "You look good today!", "Today is Friday, a wonderful day", etc, which is very cute.
I did some search, below is how to do it:
(My code is XCode 7, with a launchscreen.xib file)
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var customizedLaunchScreenView: UIView?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
application.statusBarHidden = true
// customized launch screen
if let window = self.window {
self.customizedLaunchScreenView = UIView(frame: window.bounds)
self.customizedLaunchScreenView?.backgroundColor = UIColor.greenColor()
self.window?.makeKeyAndVisible()
self.window?.addSubview(self.customizedLaunchScreenView!)
self.window?.bringSubviewToFront(self.customizedLaunchScreenView!)
UIView.animateWithDuration(1, delay: 2, options: .CurveEaseOut,
animations: { () -> Void in
self.customizedLaunchScreenView?.alpha = 0 },
completion: { _ in
self.customizedLaunchScreenView?.removeFromSuperview() })
}
return true
}
// other stuff ...
}
Just do what ever you wanted to show, text, images, animations, etc. inside the customizedLaunchScreenView here.
At the end of the launching, just fade out this customized UIView using alpha value change, then remove it completely.
How cool is that? I absolutely love it!
Hope it helps.
I was also trying to achieve this. I tried the following, it gives a delay of couple of seconds but works for me.
Create and set a launch screen in project settings.
Create a view controller with custom class (SplashViewController) and set it your starting view controller in storyboard.
Add a container view in it and set it to full screen.
Set embedded segue to a Storyboard Reference.
Select Storyboard Reference and set launch screen in StoryBoard property from Attribute inspector.
Do whatever you want in SplashViewController (play animation or session check etc) and perform segue when done.
Hope it helps!
Language: Swift 4
Hello, here's a solution for using xib for launch screen.
Adding new class named LaunchView to your project, then u'll have a new file LaunchView.swift in project.
Go to LauchImage.xib to set class to LaunchView (which it's your new class).
Adding some code, & reading some information show at the LaunchView.swift. Here's is my code for displaying version of app at launch screen.
class LaunchView: UIView {
lazy var versionLabel: UILabel = {
let label = Factory.label(style: LabelStyle.custom(font: .regular, size: 10, color: .other(ColorUtility.versionGray)), mutiLine: false, textAlignment: .center)
label.text = "ver \(Constants.appVersion)"
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
nibSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
nibSetup()
}
private func nibSetup() {
addSubview(versionLabel)
versionLabel.snp.makeConstraints { (make) in
make.bottom.equalTo(safeAreaLayoutGuide.snp.bottom).offset(-6)
make.centerX.equalToSuperview()
}
}
}