Present a popover on button tap in Swift - swift3

I would like to make a popover from a button tap. but when I click on my button, my popover cover all my page. where is my problem?
#IBAction func buttonTap(sender: UIButton) {
// get a reference to the view controller for the popover
let popController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "popoverId")
// set the presentation style
popController.modalPresentationStyle = UIModalPresentationStyle.popover
// set up the popover presentation controller
popController.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.up
popController.popoverPresentationController?.delegate = self
popController.popoverPresentationController?.sourceView = sender // button
popController.popoverPresentationController?.sourceRect = sender.bounds
// present the popover
self.present(popController, animated: true, completion: nil)
}

I think you need to change the modalPresentaionStyle
popController.modalPresentationStyle = .overCurrentContext

Related

Dismiss Completion SwiftUI

I'm showing a fullScreenCover with SwiftUI. Under certain flows, I want to dismiss the cover and show another from the Root ContentView. When I call
#Environment(\.dismiss) var dismiss
Button {
dismiss()
NotificationCenter.default.post(name: NSNotification.showNewScreen, object: nil, userInfo: nil)
} label: {
Text("This is a test")
}
I get the error:
[Presentation] Attempt to present xxx from yyy which is already presenting zzz
UIKit had the helpful completion handler so I could wait until the ViewController was dismissed before adding logic:
func dismiss(
animated flag: Bool,
completion: (() -> Void)? = nil
)
Is there something similar with SwiftUI? My current workaround is to add a delay to avoid presenting on the same view... which feels wrong.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
NotificationCenter.default.post(name: NSNotification.showNewScreen, object: nil, userInfo: nil)
}
It's not pretty to mess with modals in this way, but if you want to present the next modal from the presenting view then you can pass a binding isPresented and use the onDismiss argument for the fullScreenCover view modifier:
.fullScreenCover(isPresented: $isPresented, onDismiss: DispatchQueue.main.async { presentNextThingy }) { firstThingyContent }
or my preference use a binding that when changed will dismiss and load the next modal view, if needed. This allows the first view to determine what is presented when it is dismissed by changing the binding directly.
.fullScreenCover(item: $thingyBeingPresented) { thingy in
content(for: Thingy)
}

Navigation bar title appears with delay in mixed UIKit and SwiftUI project

I'm presenting a SwiftUI view from UIViewController.
let vc = UIHostingController(rootView: SwiftUIView())
navigationController?.pushViewController(vc, animated: true)
struct SwiftUIView: View {
var body: some View {
VStack {
}
.navigationBarTitle("Title")
}
}
The title appears only after the view has appeared. How to fix?
If you use UINavigationController then you need to set the title on the UIHostingController for it to appear instantly. Otherwise the navigation title will only appear after the SwiftUI view is rendered.
You can set the navigation title of any UIViewController using title
let vc = UIHostingController(rootView: SwiftUIView())
vc.title = "Title"
navigationController?.pushViewController(vc, animated: true)

Snapshotting a view that has not been rendered results in an empty snapshot when view presented modally - Swift 3

I have a Table View Controller as a tab. In it, there's a button that takes you to a Facebook profile:
func didTapFacebook() {
let url = URL(string: "http://www.facebook.com/" + myFacebookId)
if UIApplication.shared.canOpenURL(url!) {
UIApplication.shared.open(url!, options: [:], completionHandler: nil)
}
}
Works fine every time you press the button.
From another tab, there's a button that presents modally a navigation controller with Table View Controller as its root:
func segueToTable(_ sender: UIViewController, tvc: MyTableViewController, completion: #escaping ((_ done: Bool) -> Void)) {
let nc = MyNavigationController(rootViewController: tvc)
sender.present(nc, animated: true, completion: {
completion(true)
})
}
…
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let tvc = storyboard.instantiateViewController(withIdentifier: "TVC") as! MyTableViewController
segueToTable(self, tvc: tvc, completion: { done in
print(“segue complete”)
})
Now once you tap the same button to go to Facebook profile (or Twitter button, basically anything that causes the app to go to background), this warning occurs (there is no keyboard displayed on the screen at this time):
Cannot snapshot view (>) with afterScreenUpdates:NO, because the view is not in a window. Use afterScreenUpdates:YES.
Returning to the app and pressing the button again causes this warning to appear (and every attempt after that):
Snapshotting a view that has not been rendered results in an empty snapshot. Ensure your view has been rendered at least once before snapshotting or snapshot after screen updates.
I realize this is related to snapshotting a view that automatically occurs prior to entering the background. It appears for some reason it doesn't like doing that within a view that was presented modally. I've tried a variety of things based on other posts but cannot get the warnings to go away.
Any help is appreciated.

How to change the way in which the root controller of a navigation controller is appearing on screen?

The navigation controller is not the initial view controller. It is being instantiated in the initial VC and supplied with a root controller, the second VC. When I call present view controller function I cannot change the way in which this new VC in being presented, it always appears from the bottom. I did not find how to make it come from right to left. I tried to change who gets the call to modalPresentaionStyle from the second VC to the nav con and back but no change.
Code listing below. Thank you for your help.
#objc fileprivate func showRegisterScreen() {
let registerAccountVC = RegisterAccountVC()
let navigationController = UINavigationController(rootViewController: registerAccountVC)
navigationController.modalPresentationStyle = .pageSheet
present(navigationController, animated: true, completion: nil)
}
This will add a custom animation from right to left.
let transition = CATransition.init()
transition.duration = 0.4
transition.timingFunction = CAMediaTimingFunction.init(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = kCATransitionPush
transition.subtype = kCATransitionFromRight
view.window?.layer.add(transition, forKey: nil)
present(navigationController, animated: false, completion: nil)

Can't push from programmatically created UINavigationController

In a Swift 3 project using Xcode 8.3 and iOS 10.3, I get a navigation controller to push. The app runs with a navigation controller until I try to use it to push. All works until the last line which cause an app crash, fatal error: unexpectedly found nil while unwrapping an Optional value. I don't want to use the storyboard, the point of this question is how to accomplish this task without the storyboard.
App Delegate code
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()
window?.rootViewController = UINavigationController(rootViewController: MainViewController())
}
View Controller:
import UIKit
class MainViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
let navController = navigationController
let myProfileViewController = MyProfileViewController()
navController!.pushViewController(myProfileViewController, animated: true)
}
}
Move
let navController = navigationController
let myProfileViewController = MyProfileViewController()
navController!.pushViewController(myProfileViewController, animated: true)
from viewDidLoad to viewDidAppear.
In viewDidLoad there is no parent navigation controller set up and you should not start animations from there.
You can add navigation controller in storyboard,
in Storyboard click on MainViewController & from editor menu add navigation controller & push via navigation controller as below code.
let vc = self.storyboard?.instantiateViewController(withIdentifier: "MyProfileViewController") as! MyProfileViewController
self.navigationController?.pushViewController(vc, animated: true)
Make sure storyboard identifier is correct. & remove navigation controller code form AppDelegate class.
first give name to your navigation controller like this
then in your code initialise it like this
let nav = (UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "yourNavigation") as! UINavigationController);
nav.pushFrontViewController(MainViewController(), animated: true);
window?.rootViewController = nav