I have a screen in my iOS app that has side menu, when I swipe this side menu I want it to cover the status bar ( but I don't want status bar to be hidden completely ), I just what the part that overlaps with side menu, to get under side menu, not front of it, can anyone help me? (I'm using swift 4.2 in my app)
(this side menu is just another ViewController that I animate in and out of my MainViewController)
A possible way to show the side menu over the status bar is to use a UIWindow with wndowLevel = .statusBar that will present the status menu UIViewController. Here is a quick implementation I made:
func presentSideMenu() {
let vc = UIViewController() // side menu controller
vc.view.backgroundColor = .red
window = UIWindow()
window?.frame = CGRect(x: -view.bounds.width, y: 0, width: view.bounds.width, height: view.bounds.height)
window?.rootViewController = vc
window?.windowLevel = .statusBar
window?.makeKeyAndVisible()
window?.isHidden = false
window?.addSubview(vc.view)
}
Then you can add a pan recognizer to your view and change the frame of the UIWindow accordingly. Again a simple snippet:
func hideSideMenu() {
window?.isHidden = true
window = nil
}
#objc func pan(recognizer: UIPanGestureRecognizer) {
if recognizer.state == .began {
presentSideMenu()
} else if recognizer.state == .changed {
let location = recognizer.location(in: view)
window?.frame = CGRect(x: -view.frame.width + location.x, y: 0, width: view.frame.width, height: view.frame.height)
} else if recognizer.state == .ended {
hideSideMenu()
}
}
Note that you should hold a strong reference to the UIWindow otherwise it will be released immediately. Maybe you should consider if presenting over the status bar is a good idea though. Hope this helps.
Related
basically my current setup is like this
one storyboard ViewController with 3 types of UI View(container, front view, back view) inside of it.
what i want to accomplish (and i don't know how to implement #2)
user enters the data on the form(front of the card- View Controller number 1)
clicks the save button (do animation flipping and redirect to a new view controller)
the new view controller loads up (back of the card - View Controller number 2)
this is the current code flip example:
import UIKit
class HomeViewController: UIViewController {
#IBOutlet weak var goButton: UIButton!
#IBOutlet weak var optionsSegment: UISegmentedControl!
let owlImageView = UIImageView(image: UIImage(named:"img-owl"))
let catImageView = UIImageView(image: UIImage(named:"img-cat"))
var isReverseNeeded = false
override func viewDidLoad() {
super.viewDidLoad()
title = "Transitions Test"
setupView()
}
fileprivate func setupView() {
let screen = UIScreen.main.bounds
goButton.layer.cornerRadius = 22
//container to hold the two UI views
let containerView = UIView(frame: CGRect(x: 0, y: 0, width: 250, height: 250))
containerView.backgroundColor = UIColor(red: 6/255, green: 111/255, blue: 165/255, alpha: 1.0)
containerView.layer.borderColor = UIColor.white.cgColor
containerView.layer.borderWidth = 2
containerView.layer.cornerRadius = 20
containerView.center = CGPoint(x: screen.midX, y: screen.midY)
view.addSubview(containerView)
//front view
catImageView.frame.size = CGSize(width: 100, height: 100)
catImageView.center = CGPoint(x: containerView.frame.width/2, y: containerView.frame.height/2)
catImageView.layer.cornerRadius = 50
catImageView.clipsToBounds = true
//back view
owlImageView.frame.size = CGSize(width: 100, height: 100)
owlImageView.center = CGPoint(x: containerView.frame.width/2, y: containerView.frame.height/2)
owlImageView.layer.cornerRadius = 50
owlImageView.clipsToBounds = true
containerView.addSubview(owlImageView)
}
#IBAction func goButtonClickHandler(_ sender: Any) {
doTransition()
}
fileprivate func doTransition() {
let duration = 0.5
var option:UIViewAnimationOptions = .transitionCrossDissolve
switch optionsSegment.selectedSegmentIndex {
case 0: option = .transitionFlipFromLeft
case 1: option = .transitionFlipFromRight
case 2: option = .transitionCurlUp
case 3: option = .transitionCurlDown
case 4: option = .transitionCrossDissolve
case 5: option = .transitionFlipFromTop
case 6: option = .transitionFlipFromBottom
default:break
}
if isReverseNeeded {
UIView.transition(from: catImageView, to: owlImageView, duration: duration, options: option, completion: nil)
} else {
UIView.transition(from: owlImageView, to: catImageView, duration: duration, options: option, completion: nil)
}
isReverseNeeded = !isReverseNeeded
}
}
There are a few alternatives for transition between view controllers with a flipping animation:
You can define a segue in IB, configure that segue to do a horizontal flipping animation:
If you want to invoke that segue programmatically, give the segue a “Identifier” string in the attributes inspector and then you can perform it like so:
performSegue(withIdentifier: "SecondViewController", sender: self)
Alternatively, give the actual destination view controller’s scene a storyboard identifier, and the presenting view controller can just present the second view controller:
guard let vc = storyboard?.instantiateViewController(identifier: "SecondViewController") else { return }
vc.modalTransitionStyle = .flipHorizontal
vc.modalPresentationStyle = .currentContext
show(vc, sender: self)
If this standard flipping animation isn’t quite what you want, you can customize it to your heart’s content. iOS gives us rich control over custom transitions between view controller by specifying transitioning delegate, supplying an animation controller, etc. It’s a little complicated, but it’s outlined in WWDC 2017 Advances in UIKit Animations and Transitions: Custom View Controller Transitions (about 23:06 into the video) and WWDC 2013 Custom Transitions Using View Controllers.
In my iOS app I have lottie animation, which is on the whole screen. During animation user interactions are not available. I want to make everything under animation active to user interaction (scroll, buttons, etc.)
Is there any chance to do it?
CODE:
guard let mainView = self?.view, let animationView = LOTAnimationView(name: "someName") else { return }
animationView.frame = CGRect(x: 0,
y: 0,
width: mainView.frame.size.width,
height: mainView.frame.size.height)
animationView.contentMode = .scaleAspectFill
animationView.loopAnimation = false
mainView.addSubview(animationView)
animationView.play() { _ in
animationView.removeFromSuperview()
}
I bet disabling user interaction for the animation view would do the trick. Try something like:
animationView.userInteractionEnabled = false. This should prevent touches from being consumed by the animationView and not being propagated down the view hierarchy.
Did you tried adding gesture recognizers to Lottie view ?
Edit after code attach :
you can try add gestureRecognizer to animationView and add parameter (sender) on your function.
func handleTap(sender: UITapGestureRecognizer) {
if sender.state == .ended {
// handling code
}
}
In my app, I am displaying a popover that shows a custom UIView allowing the user to select a color using various sliders. I also want to implement an 'eyedropper' tool that the user can tap and hold then drag around to choose a color from anything visible in the app. Inside my custom UIView I added a UIPanGestureRecognizer to my button that points to the handlePan method:
var eyedropperStartLocation = CGPoint.zero
#objc func handlePan(recognizer: UIPanGestureRecognizer) {
// self is a custom UIView that contains my color selection
// sliders and is placed inside a UITableView that's in
// the popover.
let translation = recognizer.translation(in: self)
if let view = recognizer.view {
switch recognizer.state {
case .began:
eyedropperStartLocation = view.center
case .ended, .failed, .cancelled:
view.center = eyedropperStartLocation
return
default: break
}
view.center = CGPoint(x: view.center.x + translation.x,
y: view.center.y + translation.y)
recognizer.setTranslation(CGPoint.zero, in: self)
}
}
I can drag the button around and it changes location, however I have two issues:
The eyedropper button isn't always in front of other items, even inside the popover or the custom UIView inside the popover
The eyedropper button disappears when outside the bounds of the popover
How can I get the button to be visible all the time, including outside the popover? I'll want to detect where the user lets go of it within the app so I can determine what color it was on.
I figured out how to do this so I'll answer my own question. Instead of moving around the view/button that's inside the popup, I create a new UIImageView and add it to the application's Window, letting it span the whole application. The original button stays where it is - you could easily change the state on it to make it look different, or hide it if you wanted to.
You could also use Interface Builder to tie to #IBActions, but I just did everything in code. The clickButton method kicks things off but calculating location in the window and putting it on the screen. The handlePan method does the translation and lets you move it around.
All code below is swift 4 and works in XCode 9.4.1 (assuming I didn't introduce any typos):
// Call this from all your init methods so it will always happen
func commonInit() {
let panner = UIPanGestureRecognizer(target: self, action: #selector(handlePan(recognizer:)))
theButton.addGestureRecognizer(panner)
theButton.addTarget(self, action: #selector(clickButton), for: .touchDown)
eyedropperButton.addTarget(self, action: #selector(unclickButton), for: .touchUpInside)
eyedropperButton.addTarget(self, action: #selector(unclickButton), for: .touchUpOutside)
}
var startLocation = CGPoint.zero
lazy var floatingView: UIImageView = {
let view = UIImageView(image: UIImage(named: "imagename"))
view.backgroundColor = UIColor.blue
return view
}()
// When the user clicks button - we create the image and put it on the screen
// this makes the action seem faster vs waiting for the panning action to kick in
#objc func clickButton() {
guard let app = UIApplication.shared.delegate as? AppDelegate, let window = app.window else { return }
// We ask the button what it's bounds are within it's own coordinate system and convert that to the
// Window's coordinate system and set the frame on the floating view. This makes the new view overlap the
// button exactly.
floatingView.frame = theButton.convert(theButton.bounds, to: nil)
window.addSubview(floatingView)
// Save the location so we can translate it as part of the pan actions
startLocation = floatingView.center
}
// This is here to handle the case where the user didn't move enough to kick in the panGestureRecognizer and cancel the action
#objc func unclickButton() {
floatingView.removeFromSuperview()
}
#objc func handlePan(recognizer: UIPanGestureRecognizer) {
let translation = recognizer.translation(in: self)
switch recognizer.state {
case .ended, .failed, .cancelled:
doSomething()
floatingView.removeFromSuperview()
return
default: break
}
// This section is called for any pan state except .ended, .failed and .cancelled
floatingView.center = CGPoint(x: floatingView.center.x + translation.x,
y: floatingView.center.y + translation.y)
recognizer.setTranslation(CGPoint.zero, in: self)
}
Want to create an animation were user can slide the view from bottom similar to Apple Maps application in iOS. But having issues while handling gesture.
The code in my mainVC in which panVC is added as subview from bottom which works fine.(The panVC correctly comes at bottom)
func displayFromBottom {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
listVC = storyboard.instantiateViewController(withIdentifier: “PanViewContr") as! PanViewContr
var startingFrame = self.view.bounds;
startingFrame.origin.y = startingFrame.size.height; //Starts from the bottom of the parent.
startingFrame.size.height = 100; //Has a height of 100.
var finalFrame = self.view.bounds;
finalFrame.origin.y = finalFrame.size.height - 100; //100 from the bottom of the parent.
listVC.view.frame = startingFrame
listVC.willMove(toParentViewController: self)
self.addChildViewController(listVC)
self.view.addSubview(listVC.view)
listVC.didMove(toParentViewController: self)
UIView.animate(withDuration: 0.5, animations: {
self.listVC.view.frame = finalFrame
}) { complete in
print("done”)
}
}
The code for PanVC were pan gesture is handled.
func slideViewVerticallyTo(_ y: CGFloat) {
self.view.frame.origin = CGPoint(x: 0, y: y)
}
#IBAction func panGesture(_ panGesture: UIPanGestureRecognizer) {
switch panGesture.state {
case .began, .changed:
// If pan started or is ongoing then
// slide the view to follow the finger
let translation = panGesture.translation(in: view)
let y = max(0, translation.y) //what should be value of y to make it dragable smoothly
self.slideViewVerticallyTo(y)
break
case .ended:
break
}
Any hint in right direction is highly appreciated.
I had created a movable extension that adds the ability to move UIView's around a screen the code for which you can see here: https://github.com/szweier/SZUtilities/blob/master/SZUtilities/Classes/UIView/Movable/UIView%2BMovable.swift you may be able to see something in this code that you are missing in your own. I'm sorry my answer isn't more specific but hopefully it gets you moving towards the correct answer.
I need to show some popover and for that I need position of right navigation bar button. currently I used
x = self.view.frame.width - 30
y = self.view.frame.origin.y + 60
But I think It's not a good way, Because iPad screen in landscape view the view.frame.origin.y is a bit diff to iPhone in the same situation.
You can get it like this
if let rightBarButton = self.navigationItem.rightBarButtonItem {
if rightBarButton.valueForKey("view") != nil {
// here you can access the frame like this buttonView.frame
}
}
Maybe this can give you correct location converted to super view, updated from Rajat answer:
if let rightBarButton = self.navigationItem.rightBarButtonItem {
if let buttonView = rightBarButton.valueForKey("view") {
let frame = self.navigationController?.view.convertRect(buttonView.frame, toView: self.view)
print("frame \(frame)") //(330.5, 6.0, 40.0, 30.0)
}
}