How to dismiss presentation controller with a custom animation - swift

I'm using Custom Segue class to navigate between controllers.
class CustomSegue: UIStoryboardSegue {
override func perform() {
let firstVCView = self.source.view as UIView!
let secondVCView = self.destination.view as UIView!
let screenWidth = UIScreen.main.bounds.size.width
let screenHeight = UIScreen.main.bounds.size.height
secondVCView?.frame = CGRect(x: 0.0, y: screenHeight, width: screenWidth, height: screenHeight)
let window = UIApplication.shared.keyWindow
window?.insertSubview(secondVCView!, aboveSubview: firstVCView!)
UIView.animate(withDuration: 0.4, animations: { () -> Void in
firstVCView?.frame = (firstVCView?.frame.offsetBy(dx: 0.0, dy: -screenHeight))!
secondVCView?.frame = (secondVCView?.frame.offsetBy(dx: 0.0, dy: -screenHeight))!
}) { (Finished) -> Void in
self.source.present(self.destination as UIViewController,
animated: false,
completion: nil)
}
}
}
my question is how to dismiss with the opposite animation,
in dismiss method, I don't have the source/destination view to take the width and height.Thanks!

You probably found out already, but for other people landing on this topic, the solution is given in this post, namely doing the animation in the ViewController and then dismiss without animation.
Swift 4 code:
#IBAction func BackAction(_ sender: Any) {
let transition: CATransition = CATransition()
transition.duration = 0.5
transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
transition.type = CATransitionType.reveal
transition.subtype = CATransitionSubtype.fromLeft
self.view.window!.layer.add(transition, forKey: nil)
self.dismiss(animated: false, completion: nil)
}

Related

Swift Xib UiView BottomSheet being called multiple times

so i make this bottomsheet view with xib and theres nothing wrong with my code, its just i only want to show it once, i mean like everytime i click the button its get triggered. which is fine but if i rapidly click the button it will also load bunch of time according on how many times i click. i only want to show once i mean no matter how much you rapidly click it only gonna show the xib view once, until i dismiss the button on the xib and it will do the same thing.
here's some video to make it more clearly
https://drive.google.com/file/d/12pwGdTiP_1QZlYc8tV-BlIIQQ5yYrfto/view?usp=sharing
i put a gif on that gdrive link
for the code
Xib Controller :
OrderActionSheetView: UIViewController {
#IBOutlet weak var Text: UILabel!
#IBOutlet weak var vieww: UIView!
#IBOutlet weak var botView: UIView!
#IBAction func cobaLagiBTn(_ sender: Any) {
let closeView = screenSize.height
UIView.animate(withDuration: 0.3, animations: {
self.view.alpha = 0.0
self.view.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
let frame = self.view.frame
self.view.frame = CGRect(x: 0, y: closeView, width: frame.width, height: frame.height)
})
}
let fullView: CGFloat = 0
let screenSize: CGRect = UIScreen.main.bounds
override func viewDidLoad() {
super.viewDidLoad()
vieww.layer.cornerRadius = 20
vieww.layer.borderWidth = 0.5
vieww.layer.borderColor = UIColor(red:222/255, green:225/255, blue:227/255, alpha: 1).cgColor
vieww.clipsToBounds = true
botView.layer.masksToBounds = false
botView.layer.shadowColor = UIColor.black.cgColor
botView.layer.shadowOpacity = 0.14
botView.layer.shadowOffset = CGSize(width: 0, height: 0)
botView.layer.shadowRadius = 2.7
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 0.3) { [weak self] in
let frame = self?.view.frame
let yComponent = self?.fullView
self?.view.frame = CGRect(x: 0, y: yComponent!, width: frame!.width, height: frame!.height)
}
}
func prepareBackgroundView(){
let blurEffect = UIBlurEffect.init(style: .light)
let visualEffect = UIVisualEffectView.init(effect: blurEffect)
let bluredView = UIVisualEffectView.init(effect: blurEffect)
bluredView.contentView.addSubview(visualEffect)
visualEffect.frame = UIScreen.main.bounds
bluredView.frame = UIScreen.main.bounds
view.insertSubview(bluredView, at: 0)
}
func Show(){
self.view.transform = CGAffineTransform(scaleX: 1.3, y: 1.3)
self.view.alpha = 0.0
UIView.animate(withDuration: 0.25, animations: {
self.view.alpha = 1.0
self.view.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
})
}
}
View Controller :
class BottomSheetViewController: UIViewController {
#IBAction func Button(_ sender: Any) {
let text = "Connection Failed"
addBottomSheetView(text: text)
}
override func viewDidLoad() {
super.viewDidLoad()
}
func addBottomSheetView(text : String) {
// 1- Init bottomSheetVC
let bottomSheetVC = OrderActionSheetView()
// 2- Add bottomSheetVC as a child view
self.addChild(bottomSheetVC)
self.view.addSubview(bottomSheetVC.view)
bottomSheetVC.didMove(toParent: self)
// 3- Adjust bottomSheet frame and initial position.
let height = view.frame.height
let width = view.frame.width
bottomSheetVC.view.frame = CGRect(x: 0, y: self.view.frame.maxY, width: width, height: height)
bottomSheetVC.Text.text = text
}
}
i just need to know how to stop popping up twice, cause its kinda really some big bugs.....
like i said earlier i just want the xib view to shown only once no matter how many times you rapidly click the button.
Thanks guys :)
As here you add a new instance every click
func addBottomSheetView(text : String) {
// 1- Init bottomSheetVC
let bottomSheetVC = OrderActionSheetView()
// 2- Add bottomSheetVC as a child view
self.addChild(bottomSheetVC)
self.view.addSubview(bottomSheetVC.view)
bottomSheetVC.didMove(toParent: self)
So either
1-You need to add a bool variable like
var isShown = false
and in beginning of method add this code
guard !isShown else { return }
isShown = true
and when you remove the view make
isShown = false
Or
2- create an instance variable like
var bottomSheetVC:OrderActionSheetView?
and in beginning of method
guard bottomSheetVC == nil else { return }
bottomSheetVC = OrderActionSheetView()
and when you remove it do
bottomSheetVC.view.removeFromSuperview()
bottomSheetVC = nil

How To Create Animation of Segue perform and unwind segue?

i am working on application where i am trying to call controllers with animations
second view controller with animations
like when i call second controller with segue
second controller come from right and first controller start moving to left
like android push pop animations
i make some code
class SegueFromRight: UIStoryboardSegue {
override func perform() {
let src = self.source
let dst = self.destination
src.view.superview?.insertSubview(dst.view, aboveSubview: src.view)
dst.view.transform = CGAffineTransform(translationX: src.view.frame.size.width*2, y: 0)
//Double the X-Axis
UIView.animate(withDuration: 0.25, delay: 0.0, options: UIView.AnimationOptions.curveEaseInOut, animations: {
dst.view.transform = CGAffineTransform(translationX: 0, y: 0)
}) { (finished) in
src.present(dst, animated: false, completion: nil)
}
}
}
but from this code new controller come from right i need to move current controller to move left too
if anyone can help
Try this code:
class CustomSegue: UIStoryboardSegue {
override func perform() {
let firstVCView: UIView = self.source.view
let secondVCView: UIView = self.destination.view
self.destination.modalPresentationStyle = .fullScreen
let screenWidth = UIScreen.main.bounds.size.width
let screenHeight = UIScreen.main.bounds.size.height
secondVCView.frame = CGRect(x: screenWidth, y: 0, width: screenWidth, height: screenHeight)
let window = UIApplication.shared.keyWindow
window?.insertSubview(secondVCView, aboveSubview: firstVCView)
UIView.animate(withDuration: 0.4, animations: { () -> Void in
firstVCView.frame = firstVCView.frame.offsetBy(dx: -screenWidth, dy: 0.0)
secondVCView.frame = secondVCView.frame.offsetBy(dx: -screenWidth, dy: 0.0)
}) { (Finished) -> Void in
self.source.present(self.destination as UIViewController,
animated: false,
completion: nil)
}
}
}
class CustomSegueUnwind: UIStoryboardSegue {
override func perform() {
let secondVCView: UIView = self.source.view
let firstVCView: UIView = self.destination.view
self.destination.modalPresentationStyle = .fullScreen
let screenWidth = UIScreen.main.bounds.size.width
let window = UIApplication.shared.keyWindow
window?.insertSubview(firstVCView, aboveSubview: secondVCView)
UIView.animate(withDuration: 0.4, animations: { () -> Void in
firstVCView.frame = firstVCView.frame.offsetBy(dx: screenWidth, dy: 0.0)
secondVCView.frame = secondVCView.frame.offsetBy(dx: screenWidth, dy: 0.0)
}) { (Finished) -> Void in
self.source.dismiss(animated: false, completion: nil)
}
}
}
Reference HERE for more details

second vc is smaller that the initial vc

I got this problem on iPad after update yesterday: if I load the second view controller, it appears smaller that the initial one (see photos below). Interesting, that is happening only since my update to the new iOS on iPad. There is no such problem on iPhone: the both VCs have there the same size.
Here is the code:
import UIKit
class StartVC: UIViewController, ButtonDelegate {
var width = CGFloat()
var height = CGFloat()
override func viewDidLoad() {
super.viewDidLoad()
width = view.frame.width
height = view.frame.height
setTestBtn()
view.backgroundColor = UIColor.purple
}
func setTestBtn(){
let a = Button(frame: CGRect(x: 0,y: 0, width: width*0.2, height: width*0.2))
a.center = CGPoint(x: width*0.5, y: height*0.5)
a.backgroundColor = .cyan
a.delegate = self
a.setLabel()
a.label.text = "Go to the MainVC"
view.addSubview(a)
}
func buttonClicked(sender: Button) {
let vc = self.storyboard?.instantiateViewController(withIdentifier: "mainVC") as! MainVC
let transition = CATransition()
transition.duration = 2
transition.type = CATransitionType.push
transition.subtype = CATransitionSubtype.fromRight
view.window!.layer.add(transition, forKey: kCATransition)
vc.view.frame = view.bounds
self.present(vc, animated: true, completion: nil)
print("# ViewController func buttonClicked() OK!")
}
import UIKit
class MainVC: UIViewController, ButtonDelegate {
var width = CGFloat()
var height = CGFloat()
override func viewDidLoad() {
super.viewDidLoad()
width = view.frame.width
height = view.frame.height
setTestBtn()
view.backgroundColor = .red
}
func setTestBtn(){
let a = Button(frame: CGRect(x: 0,y: 0, width: width*0.2, height: width*0.2))
a.center = CGPoint(x: width*0.5, y: height*0.5)
a.backgroundColor = .orange
a.delegate = self
a.setLabel()
a.label.text = "Go to the StartVC"
view.addSubview(a)
}
func buttonClicked(sender: Button) {
let vc = self.storyboard?.instantiateViewController(withIdentifier: "startVC") as! StartVC
let transition = CATransition()
transition.duration = 2
transition.type = CATransitionType.push
transition.subtype = CATransitionSubtype.fromRight
view.window!.layer.add(transition, forKey: kCATransition)
vc.view.frame = view.bounds
self.present(vc, animated: true, completion: nil)
print("# ViewController func buttonClicked() OK!")
}
}
Since the release of iOS 13, the modal presentation style of View Controllers is chosen automatically by the system by default.
If you wish to counter this effect, this can help you.
vc.modalPresentationStyle = .overFullScreen
self.present(vc, animated: true, completion: nil)
Hope this helps!

Adding a search bar to ui table view on overlay

I have managed to create, a dark overlay once clicked a search icon and have managed to add a table uiview on the dark overlay but now want to add a search bar within that table view. I cant seem to figure it out as my code seems different to everyone else's examples. I am new to swift so my code probably isn't the cleanest. can i ask if someone can show me how to do this as i am out of ideas. I have posted my code below can someone please show me where i'm going wrong.
Many thanks
class SearchLauncher: NSObject {
let blackView = UIView()
let tableView = UITableView()
#objc func showSearch() {
if let window = UIApplication.shared.keyWindow {
blackView.backgroundColor = UIColor(white: 0, alpha: 0.5)
blackView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleDismiss)))
window.addSubview(blackView)
window.addSubview(tableView)
let height: CGFloat = 600
let y = window.frame.height - height
tableView.frame = CGRect(x: 0, y: window.frame.height, width: window.frame.width, height: height)
blackView.frame = window.frame
blackView.alpha = 0
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.blackView.alpha = 1
self.tableView.frame = CGRect(x: 0, y: y, width: self.tableView.frame.width, height: self.tableView.frame.height)
}, completion: nil)
}
}
#objc func handleDismiss() {
UIView.animate(withDuration: 0.5) {
self.blackView.alpha = 0
if let window = UIApplication.shared.keyWindow {
self.tableView.frame = CGRect(x: 0, y: window.frame.height, width: self.tableView.frame.width, height: self.tableView.frame.height)
}
}
}
override init() {
super.init()
//start doing something here maybe
}
}```
How did you try to add a search controller?
I would do it by adding an UISearchController as a tableHeaderView of your table view. First make sure to add UISearchResultsUpdating protocol to your class.
class SearchLauncher: NSObject, UISearchResultsUpdating
Then add the search bar to your table view
let searchController = UISearchController(searchResultsController: nil)
searchController.searchBar.sizeToFit()
searchController.searchResultsUpdater = self
//you probably don't need this
//searchController.dimsBackgroundDuringPresentation = false
tableView.tableHeaderView = searchController.searchBar
And then, implement the updateSearchResults(for:_) delegate method
func updateSearchResults(for searchController: UISearchController) {
filteredTableData.removeAll(keepingCapacity: false)
//filter the table data by using searchController.searchBar.text!
//filteredTableData = ...
self.tableView.reloadData()
}

Swift - Open UIViewController from within slide-in menu

I have a slide-in menu that presents itself when I press the menu button in my navigation bar. Dismissing it can be accomplished by either tapping the background or the menu itself. Within this view I have several items, one of which is an user profile button. When I press this button, I want the menu to dismiss itself and then instantly open the user profile view controller. That's why I call handleDismiss() first and then set the view that needs to be opened
However, it keeps telling me that the view is not in the window hierarchy. I know what the problem is (view stack), but I somehow can't get it to work properly. How should I tackle this problem?
RootViewController -> HomeController (tabBar index 0) -> Slide-In Menu -> UserProfileController
Button to open the controller
profileCell.profileButton.addTarget(self, action: #selector(handleUserProfile(sender:)), for: .touchUpInside)
Functions
func handleDismiss() {
UIView.animate(withDuration: 0.5) {
self.blackView.alpha = 0
if let window = UIApplication.shared.keyWindow {
self.collectionView.frame = CGRect(x: 0, y: window.frame.height, width: self.collectionView.frame.width, height: self.collectionView.frame.height)
}
}
}
func handleUserProfile(sender: UIButton) {
handleDismiss()
let userProfileController = UserProfileController()
openProfileController(userProfileController)
}
func openProfileController(_ controller: UIViewController) {
present(controller, animated: false, completion: nil)
}
I fixed the problem by setting a reference to the rootViewController in my menu.
HomeController
let menuLauncher = MenuLauncher()
func tappedMenu(sender: UIButton) {
menuLauncher.showMenu(self)
}
MenuController
func MenuLauncher() {
self.m_ParentViewController = nil
}
func showMenu(_ parentViewController: UIViewController) {
self.m_ParentViewController = parentViewController
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(MenuLauncherImageCell.self, forCellWithReuseIdentifier: cellIdImage)
collectionView.register(MenuLauncherInfoCell.self, forCellWithReuseIdentifier: cellIdInfo)
if let window = UIApplication.shared.keyWindow {
blackView.backgroundColor = UIColor(white: 0, alpha: 0.5)
blackView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleDismiss)))
window.addSubview(blackView)
window.addSubview(collectionView)
collectionView.frame = CGRect(x: 0, y: window.frame.height, width: window.frame.width, height: 350)
blackView.frame = window.frame
blackView.alpha = 0
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.blackView.alpha = 1
self.collectionView.frame = CGRect(x: 0, y: window.frame.height - 350, width: self.collectionView.frame.width, height: self.collectionView.frame.height)
}, completion: nil)
}
}
func handleDismiss() {
UIView.animate(withDuration: 0.5) {
self.blackView.alpha = 0
if let window = UIApplication.shared.keyWindow {
self.collectionView.frame = CGRect(x: 0, y: window.frame.height, width: self.collectionView.frame.width, height: self.collectionView.frame.height)
}
}
}
func handleUserProfile(sender: UIButton) {
handleDismiss()
let userProfileController = UserProfileController()
self.m_ParentViewController?.present(userProfileController, animated: true, completion: nil)
}