Present UIView over Apple Statusbar - ios

I want to cover the statusbar with a view like the following code shows. I have read a lot that this needs to be done in an separate(?) window that is on the same windows layer as the statusbar but I just don't get it to work.
I tried this (first code)
Display UIView Above Apple Status Bar in iOS 8
but my self.view.window? is nil
Trying to create a new UIWindow XCODE wants a rootViewController which I would have to fake... IMHO That cannot be the right way
Here is my code:
class GroupSelectionTVC: UITableViewController
{
override func viewDidLoad()
{
// Toast
let frame = CGRectMake(0, -20, self.view.frame.width, 20)
let message = UILabel(frame: frame)
message.backgroundColor = UIColor.blackColor()
message.text = "Testing"
message.textAlignment = NSTextAlignment.Center
message.textColor = UIColor.darkGrayColor()
self.view.addSubview(message)
UIView.animateWithDuration(1.0, animations: {
message.frame = CGRectMake(0, 0, self.view.frame.width, 20)}, completion:
{
(value: Bool) in UIView.animateWithDuration(1.0, delay: 2.0, options: nil, animations: {message.frame = CGRectMake(0, -20, self.view.frame.width, 20)}, completion:
{
(value: Bool) in message.removeFromSuperview()
}
)
}
)

I would recommend you to use CRToast. It's a framework for exactly your kind of problem. So you don't have to worry about UIViews etc. It's written in Objective-C but you can easily use it in a Swift project.

Related

How to use completion block for UIView.animate()?

I'm working on a project to learn animations and am having trouble using the completion block for the UIView.animate(withDuration:) func. MY animation is a shoebox that falls from the top of the screen, lands on a pedestal, then opens. Once the box opens I want a UIImageView to come out of the box, grow to full size of the screen and then segue to the next page, but the completion handler that the code for segueing is called before my animation completes and the UIImageView doesn't appear at all.
Here's my code:
override func viewDidAppear(_ animated: Bool) {
UIView.animate(withDuration: 1.5, delay: 0.0, options: .curveEaseInOut,
animations: {
//Opening the box
self.shoeBoxImage.shoeBox.animationImages = self.boxOpeningAnimation
self.shoeBoxImage.shoeBox.animationDuration = 1.5
self.shoeBoxImage.shoeBox.animationRepeatCount = 1
self.shoeBoxImage.shoeBox.contentMode = .scaleAspectFill
self.shoeBoxImage.shoeBox.startAnimating()
//set to the final image
self.shoeBoxImage.shoeBox.image = UIImage(named: "frame13")
},completion: {_ in
let nextPage = UIImageView()
nextPage.frame = CGRect(origin: self.shoeBoxImage.center, size: CGSize(width: 0.0, height: 0.0))
nextPage.image = UIImage(named: "FirstLoadBackgroundImg.jpeg")
nextPage.autoresizesSubviews = true
self.view.addSubview(nextPage)
self.view.bringSubviewToFront(nextPage)
UIView.animate(withDuration: 5.0, animations: {
nextPage.transform = CGAffineTransform(scaleX: 428, y: 926)
})
self.performSegue(withIdentifier: "FinishedLoading", sender: self)
})
}
This is my first time working with animations and programatically creating views so if someone could explain how to make the completion block wait for the animation to complete. In order to make the UIImageView appear and animate then once it's full screen, segue to the next page it would be very much appreciated.
The size is 0, 0. Transforming zero by any scale is still zero. I would advise you to not use transform at all, but rather just set the final frame to be what you want.
E.g.,
let startFrame = CGRect(x: view.bounds.midX, y: view.bounds.midY, width: 0, height: 0)
let endFrame = view.bounds
let imageView = UIImageView(image: ...)
imageView.contentMode = .scaleAspectFill
view.addSubview(imageView)
imageView.frame = startFrame
UIView.animate(withDuration: 3, delay: 0, options: .curveEaseInOut) {
imageView.frame = endFrame
} completion: { _ in
// do something here
}
That yields:
By the way, the performSegue probably should be inside a completion closure of the inner animate call.

Adding a subview inside of a UIStoryboardSegue

I'm writing an app where I need to use a fairly "complex" UIStoryboardSegue. The designer gave me the following:
Now the basic idea behind the segue is simple, sliding up the source.view, then sliding up one more view before eventually sliding the destination.view. However, my question is the following:
How do I insert a second view in-between the source.view and the destination.view from a subclass of UIStoryboardSegue ? Obviously I can't just do addSubview, since there is no view to add to. Is there another place to add a view in a UIStoryboardSegue, so I can still create this segue?
Thanks.
If this is what you want
You can achieve it very easily,
Step 1:
Create a subclass of UIStoryboardSegue and override perform
import UIKit
class SpecialEffectSegue: UIStoryboardSegue {
override func perform() {
let firstVCView = self.source.view as UIView!
let secondVCView = self.destination.view as UIView!
let intermediateView = UIView()
intermediateView.backgroundColor = UIColor.red
// Get the screen width and height.
let screenWidth = UIScreen.main.bounds.size.width
let screenHeight = UIScreen.main.bounds.size.height
// Specify the initial position of the destination view.
secondVCView?.frame = CGRect(x: 0.0, y: screenHeight, width: screenWidth, height: screenHeight)
intermediateView.frame = CGRect(x: 0.0, y: screenHeight, width: screenWidth, height: screenHeight)
// Access the app's key window and insert the destination view above the current (source) one.
let window = UIApplication.shared.keyWindow
window?.insertSubview(intermediateView, aboveSubview: firstVCView!)
window?.insertSubview(secondVCView!, aboveSubview: secondVCView!)
UIView.animate(withDuration: 0.4, animations: { () -> Void in
firstVCView?.frame = ((firstVCView?.frame)?.offsetBy(dx: 0.0, dy: -screenHeight))!
intermediateView.frame = (intermediateView.frame.offsetBy(dx: 0.0, dy: -screenHeight))
}) { (Finished) -> Void in
UIView.animate(withDuration: 0.4, animations: { () -> Void in
secondVCView?.frame = (secondVCView?.frame.offsetBy(dx: 0.0, dy: -screenHeight))!
}) { (Finished) -> Void in
self.source.present(self.destination, animated: false, completion: {
intermediateView.removeFromSuperview()
})
}
}
}
}
Though code looks huge and complicated what is happening in it is pretty simple.
Get the source and destination viewController's view using
let firstVCView = self.source.view as UIView!
let secondVCView = self.destination.view as UIView!
Because you need a intermediate view which is red in color here you create one more view
let intermediateView = UIView()
intermediateView.backgroundColor = UIColor.red
Now get the UIScreen width and heght so that you can configure these views frame so that it looks good as per your need
let screenWidth = UIScreen.main.bounds.size.width
let screenHeight = UIScreen.main.bounds.size.height
// Specify the initial position of the destination view.
secondVCView?.frame = CGRect(x: 0.0, y: screenHeight, width: screenWidth, height: screenHeight)
intermediateView.frame = CGRect(x: 0.0, y: screenHeight, width: screenWidth, height: screenHeight)
Note that the I set the frame of SecondVC and intermediateView such that they are below screen bounds and Ill animate them to come up in UIView.animate block
Now obviously because animations are happening on key window of your app access the key window
let window = UIApplication.shared.keyWindow
Now insert the subviews to window as per your need.
window?.insertSubview(intermediateView, aboveSubview: firstVCView!)
window?.insertSubview(secondVCView!, aboveSubview: secondVCView!)
So after this I have views stacked up as FirstVCView -> IntermediateView -> SecondVCView
Now we have pretty much what we need isnt it. Now animate it using UIView.animate
UIView.animate(withDuration: 0.4, animations: { () -> Void in
firstVCView?.frame = ((firstVCView?.frame)?.offsetBy(dx: 0.0, dy: -screenHeight))!
intermediateView.frame = (intermediateView.frame.offsetBy(dx: 0.0, dy: -screenHeight))
}) { (Finished) -> Void in
UIView.animate(withDuration: 0.4, animations: { () -> Void in
secondVCView?.frame = (secondVCView?.frame.offsetBy(dx: 0.0, dy: -screenHeight))!
}) { (Finished) -> Void in
self.source.present(self.destination, animated: false, completion: {
intermediateView.removeFromSuperview()
})
}
}
Important thing to notice is intermediateView.removeFromSuperview() in completion block.
Now I have decided to present destination using self.source.present( if you need to push destination VC with your funky animation say
self.source.navigationController?.pushViewController(destination, animated: false)
Thats all :)
Now open your storyboard, drag a segue from one your FirstVC to SecondVC and select the segue class as SpecialEffectSegue thats it now enjoy
Hope it helps :)

How to Add UIToolbar with two button in UIPickerView(swift 2.0)?

I would like to add two UIButtons in the UIPickerView (on bottom of it). Please take a look at the Cancel and Done buttons in this image:
Here My code Upload:-
class DatePicker{
var containerView = UIView()
var datePicker = UIView()
var datePickerView = UIDatePicker()
var toolBar = UIToolbar()
internal class var shared: DatePicker {
struct Static {
static let instance: DatePicker = DatePicker()
}
return Static.instance
}
internal func showProgressView(view: UIView) {
containerView.frame = view.frame
containerView.center = view.center
containerView.backgroundColor = UIColor(hex: 0xffffff, alpha: 0.3)
datePickerView.datePickerMode = .Date
datePicker.frame = CGRectMake(0, 0, 250, 250)
datePicker.center = view.center
datePicker.backgroundColor = UIColor(hex: 0x444444, alpha: 0.7)
datePicker.clipsToBounds = true
datePicker.layer.cornerRadius = 10
datePickerView.setValue(UIColor.whiteColor(), forKeyPath: "textColor")
datePickerView.frame = CGRectMake(0, 0, 250, 250)
datePicker.addSubview(datePickerView)
containerView.addSubview(datePicker)
view.addSubview(containerView)
}
internal func hideProgressView() {
containerView.removeFromSuperview()
}
}
How can i get UIToolbar And Two Button?
I would recommend making a .xib with the toolbar and picker view in it. Put two bar buttons on the bar and put a bar button spacer in between them.
Link on how to use a xib
You can make the animation of it appearing look great if you start its frame off screen and use this function to move it on screen.
UIView.animateWithDuration(0.5, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: .CurveEaseInOut, animations: {
if viewToMove != nil {
viewToMove!.frame.origin.y/*orX*/ = onScreenPosition
} else {
"sidePanel == nil"
}
}, completion: completion)
usingSpringWithDamping can be set to 1 if you don't want any bounce in animation. If this is for an iPhone, i would recommend making the initial frame equal to something like
CGRectMake(0, UIScreen.mainScreen().bounds.height, UIScreen.mainScreen().bounds.width, UIScreen.mainScreen().bounds.height/2)
and then the view to move line would become
viewToMove!.frame.origin.y = UIScreen.mainScreen().bounds.height/2
using the previous function to bring it on screen and
viewToMove!.frame.origin.y = UIScreen.mainScreen().bounds.height
to move it off screen. You can access the buttons of the xib in a few different ways, but I recommend using a delegate.
Here is a guide to them.
If you are not familiar with them, I would become familiar, because they are very useful! If you have any questions don't hesitate to ask!

Hide UIStatusBar without removing the space allocated for it

I have picture examples to show you what I want and what I have right now.
First, here is an example of what I'm trying to do, from the Slack app:
The statusbar is normally displayed:
But when you open the side drawer, it goes away:
I can display the status bar in my app:
But when I hide it, it also hides the frame, so there is less space at the top than before:
It looks wonky to remove space from the top whenever the side drawer opens, but it also looks bad to not hide the status bar since the menu has a different background color. How can I hide the text on the status bar while keeping the space for it still there?
I think you want something like the following (In Swift, Deploy target is 9.0):
To hide it:
UIApplication.sharedApplication().setStatusBarHidden(true, withAnimation: .Fade)
let appFrame:CGRect = UIScreen.mainScreen().applicationFrame
UIView.animateWithDuration(0.3, animations: {
self.navigationController?.navigationBar.frame = self.navigationController!.navigationBar.bounds
self.view.window!.frame = CGRectMake(0, 0, appFrame.size.width, appFrame.size.height);
})
To show it again:
let appFrame:CGRect = UIScreen.mainScreen().applicationFrame
UIApplication.sharedApplication().setStatusBarHidden(false, withAnimation: .Fade)
UIView.animateWithDuration(0.3, animations: {
self.navigationController?.navigationBar.frame = self.navigationController!.navigationBar.bounds
self.view.window!.frame = CGRectMake(0, 0, appFrame.size.width, appFrame.size.height-0.00001);
})
I'm not sure you will run into the same issue as I will, but when I tested the code I originally didn't have that "-0.00001" and the transition was not smooth but that little subtraction fixed it. Not sure why.
I couldn't get the accepted answer to work on iOS 10 in Swift 3. So here is what I came up with:
class ViewController: UIViewController {
override var prefersStatusBarHidden: Bool {
return true
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 0.3, animations: {
let bounds = self.navigationController!.navigationBar.bounds
self.navigationController?.navigationBar.frame = CGRect(x: 0, y: 0, width: bounds.width, height: bounds.height + 20)
})
}
}
I did succeed with this solution.
extension UIApplication {
var statusBarView: UIView? {
if responds(to: Selector("statusBar")) {
return value(forKey: "statusBar") as? UIView
}
return nil
}
func hideStatusBar() {
statusBarView?.frame.origin.y = -200
statusBarView?.isHidden = true
}
func showStatusBar() {
statusBarView?.frame.origin.y = 0
statusBarView?.isHidden = false
}
}
I've encounter status bar shrink when create a slack style sidebar view on iPhone 8(which has no notch), the best way to keep top spacing is to set additionalSafeAreaInsets.top = 20, 20pt is a constant of status bar height.
Demo Image: https://i.stack.imgur.com/MmeXw.gif
func hasNotch() -> Bool {
return self.view.safeAreaInsets.bottom > 20
}
if show {
UIView.animate(withDuration: 0.5, delay: 0,usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: .curveEaseOut, animations: {
self.centerView.view.frame.origin.x = self.centerView.view.frame.width - self.menuWidth
self.menuView.view.frame.origin.x = 0
// IMPORTANT: The code below fix status bar shrink when device has no notch.
if !self.hasNotch() {
self.additionalSafeAreaInsets.top = 20
}
}, completion: nil)
} else {
UIView.animate(withDuration: 0.5, delay: 0,usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: .curveEaseOut, animations: {
self.centerView.view.frame.origin.x = 0
self.menuView.view.frame.origin.x = -self.menuWidth
// IMPORTANT: The code below fix status bar shrink when device has no notch.
if !self.hasNotch() {
self.additionalSafeAreaInsets.top = 0
}
}, completion: nil)
}

Having an issue animating certain elements UIView will excluding others

In my code, I've correctly moved the UITextfield into view when the keyboard pops up. However, I've struggled to create another animation that acts only on the UIImageView as this current solution permanently offsets the logo in the UIImageView.
var logoImage:UIImageView!
var logoImageX:CGFloat = 85
var logoImageY:CGFloat = 35
// Logo Code
let logo:UIImage = UIImage(named:"color-logo.png")
let logoHeight:CGFloat = 150
let logoWidth:CGFloat = logoHeight
logoImage = UIImageView(image:Logo)
logoImage.frame = CGRectMake(logoImageX,logoImageY,logoWidth, logoHeight)
self.view.insertSubview(logoImage, atIndex: 1)
// Keyboard Auto Scroll
func textFieldDidBeginEditing(textField: UITextField!) {
self.animateTextField(textField, up: true)
self.animateWithDuration(true)
}
func textFieldDidEndEditing(textField: UITextField!) {
self.animateTextField(textField, up: false)
self.animateWithDuration(false)
}
func animateTextField(textField:UITextField,up: Bool){
let movementDistance:Int = -100
let movementDuration:NSTimeInterval = 0.25
var movement = CGFloat(Int((up ? movementDistance : -movementDistance)))
UIView.beginAnimations("animateTextField", context:nil)
UIView.setAnimationBeginsFromCurrentState(true)
UIView.setAnimationDuration(movementDuration)
self.view.frame = CGRectOffset(self.view.frame, 0, movement)
UIView.commitAnimations()
}
Here's the function for the UIImageView:
//Logo (UIImageView) Scroll
func animateWithDuration(up:Bool){
let logoMovementDuration = 0.25
let logoMovementDistance:Int = 20
var logoMovement = CGFloat(Int((up ? logoMovementDistance : -logoMovementDistance + (logoImageY))))
UIView.beginAnimations("animateWithDuration", context:nil)
UIView.setAnimationDuration(logoMovementDuration)
logoImage.frame = CGRectMake(logoImageX,logoMovement,logoImage.frame.size.width,logoImage.frame.size.height)
UIView.commitAnimations()
}
I've found a solution by creating multiple UIViews, or in my case 3 specifically.
The code looks like this, depending on what you want to achieve, segregated into different groups in order to apply animations to each of them (these are declared outside of the viewDidLoad() to make them publicly accessible):
var otherView = UIView(frame: UIScreen.mainScreen().bounds)
var logoImageView = UIView(frame:CGRect(x: 0, y: 0, width: xx, height: xx))
var resultView = UIView(frame:CGRect(x: 0, y: 0, width: xx, height: xx))
After which, I added the subviews to UIView instances and then finally into the super view (declared within the viewDidLoad()):
self.logoImageView.insertSubview(logoImage, atIndex:1)
self.view.insertSubview(logoImageView, atIndex: 1)
Then finally applied the same func's now using the block based animations as suggested by rdelmar and recommended by Apple since iOS 4.0 (outside of, and after, the viewDidLoad()):
// Keyboard Auto Scroll
func textFieldDidBeginEditing(textField: UITextField!) {
self.animateTextField(textField, up: true)
}
func textFieldDidEndEditing(textField: UITextField!) {
self.animateTextField(textField, up: false)
}
func animateTextField(textField:UITextField,up: Bool){
let resultMovementDistance:Int = -xx
let movementDistance:Int = -xx
let movementDuration:NSTimeInterval = 0.xx
var resultMovement = CGFloat(Int((up ? resultMovementDistance : -resultMovementDistance)))
var otherMovement = CGFloat(Int((up ? movementDistance : -movementDistance)))
var logoMovement = otherMovement/5
UIView.animateWithDuration(
movementDuration,
delay: 0,
options: UIViewAnimationOptions.CurveEaseIn, animations:{
self.otherView.frame = CGRectOffset(self.otherView.frame,0,otherMovement)},
completion:nil)
UIView.animateWithDuration(
movementDuration,
delay: 0,
options: UIViewAnimationOptions.CurveEaseIn, animations:{
self.logoImageView.frame = CGRectOffset(self.logoImageView.frame,0,logoMovement)},
completion:nil)
UIView.animateWithDuration(
movementDuration,
delay: 0,
options: UIViewAnimationOptions.CurveEaseIn, animations:{
self.resultView.frame = CGRectOffset(self.resultView.frame,0,resultMovement)},
completion:nil)
}
Note: I did struggle with nesting the animations in the block, but I'm glad that it worked out. I hope this helps.

Resources