ContainerView - how close invisible view - ios

I have this storyboard:
and this code:
var actualVisibleView : String? = nil
func showSubViewInContainerView(view: String){
let controller = storyboard!.instantiateViewController(withIdentifier: view)
addChildViewController(controller)
controller.view.translatesAutoresizingMaskIntoConstraints = false
systemContainerView.addSubview(controller.view)
NSLayoutConstraint.activate([
controller.view.leadingAnchor.constraint(equalTo: systemContainerView.leadingAnchor),
controller.view.trailingAnchor.constraint(equalTo: systemContainerView.trailingAnchor),
controller.view.topAnchor.constraint(equalTo: systemContainerView.topAnchor),
controller.view.bottomAnchor.constraint(equalTo: systemContainerView.bottomAnchor)
])
controller.didMove(toParentViewController: self)
if self.actualVisibleView != nil && self.actualVisibleView != view {
controller.dismiss(animated: false) {
print("UBIJAM: \(view)")
}
}
self.actualVisibleView = view
print("OTWIERAM: \(view)")
}
From the left menu, I open various views in this containerview using the code:
showSubViewInContainerView(view: "view1")
showSubViewInContainerView(view: "view2")
showSubViewInContainerView(view: "view3")
showSubViewInContainerView(view: "view4")
This code works light. The only problem is that when I open a new view in the container view I would like to close the previously visible view.
Only one active view will be visible in containerview.
At the moment there are views overlapping one another.
Does anyone know how to fix it?

Before adding a new view in the container remove other views
systemContainerView.subviews.forEach { $0.removeFromSuperview() }
systemContainerView.addSubview(controller.view)

Related

iPadOS - SwiftUI in a Safari Extension Not Rendering Properly

I'm writing a simple credential autofill extension as a means of playing around with SwiftUI on iOS. However, I'm finding that on iPadOS the SwiftUI View does not render properly. My CredentialProviderViewController is called at the beginning of the extension lifecycle, and is responsible for loading the SwiftUI View. It looks like this:
class CredentialProviderViewController: ASCredentialProviderViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func prepareCredentialList(for serviceIdentifiers: [ASCredentialServiceIdentifier]) {
let services: [String] = serviceIdentifiers.map { $0.identifier }
let autofillView = AutofillView(services: services,
extensionContext: self.extensionContext)
let vc = UIHostingController(rootView: autofillView)
vc.view.translatesAutoresizingMaskIntoConstraints = false
vc.view.frame = view.bounds
view.addSubview(vc.view)
addChild(vc)
}
}
My SwiftUI AutofillView is very simple and looks like this:
struct AutofillView: View {
let services: [String]
var extensionContext: ASCredentialProviderExtensionContext? = nil
var body: some View {
NavigationView {
Text("LOCK SCREEN")
}
}
}
On iPhone, this renders exactly as I'd expect, with the words "LOCK SCREEN" appearing in the center of the View when the extension loads. However, on iPad the View is displayed in a modal window and the contents are not rendered properly. In fact, only the slightest bit of the "L" is displayed. (See screenshot)
I'm sure I'm missing something or not instantiating my SwiftUI View properly. I'm just not sure where. Any thoughts?
Instead of adding it into a containerview or as a subview, present the UIHostingController like you would a view controller
let services: [String] = serviceIdentifiers.map { $0.identifier }
let autofillView = AutofillView(services: services,
extensionContext: self.extensionContext)
let vc = UIHostingController(rootView: autofillView)
vc.modalPresentationStyle = .fullScreen
self.present(vc, animated: false)

How to properly make a container view that contains some viewControllers(programmatically)?

I want to create a container View programmatically that has inside a bunch of view controllers. I have a segmented controller that when the user tapped a button it should display a certain viewController without doing a segue.
I have everything inside a ViewController,
I wanted to know how to properly make the segmented controller display a certain view controller when tapped.
func setUpSegmentedControl() {
let seg = UISegmentedControl(items: ["1", "2", "3"])
seg.selectedSegmentIndex = 0
seg.translatesAutoresizingMaskIntoConstraints = false
seg.layer.cornerRadius = 8
seg.backgroundColor = UIColor.white
seg.addTarget(self, action: #selector(changeColor(sender:)), for: .valueChanged)
view.addSubview(seg)
NSLayoutConstraint.activate([
seg.centerXAnchor.constraint(equalTo: view.centerXAnchor), seg.centerYAnchor.constraint(equalTo: view.centerYAnchor), seg.leadingAnchor.constraint(equalTo: view.leadingAnchor), seg.trailingAnchor.constraint(equalTo: view.trailingAnchor), seg.heightAnchor.constraint(equalToConstant: 50)
])
}
#objc func changeColor(sender: UISegmentedControl) {
switch sender.selectedSegmentIndex {
case 0:
addChild(FirstViewController())
print("1")
case 1:
addChild(SecondViewController())
print("2")
default:
addChild(ThirdViewController())
print("3")
}
}
To add a view controller to a container view programmatically, you need to call addSubview and didMove in addition to addChild.
let firstViewController = FirstViewController()
addChild(firstViewController)
firstViewController.view.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(firstViewController.view) // replace `containerView` with the name of the view that's supposed to contain the VC's view
// add constraints or set frame manually
let trailingConstraint = firstViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor)
let leadingConstraint = firstViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor)
let topConstraint = firstViewController.view.topAnchor.constraint(equalTo: view.topAnchor)
let bottomConstraint = firstViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor)
NSLayoutConstraint.activate([trailingConstraint, leadingConstraint, topConstraint, bottomConstraint])
firstViewController.didMove(to parent: self)
If you want to remove a child view controller programatically:
let child = children.first // or other way to identify your VC
child?.willMove(toParentViewController: nil)
child?.view.removeFromSuperview()
child?.view.removeFromParentViewController()
Example project

Swift iOS App how to display a UIView over a UITabBarController

I have an iOS App with 3 ViewController, on my first ViewController if the user isn't logged I show a popUp that cover all the screen with a black UIView (opacity 50%, like a UIAlertController) and 2 buttons (register or log in).
The problem is, the tabBar is always over my UIView and i would like to display over the tabBar... (I can't juste hide the tabBar because of the opacity, i still want to see the tabBar under my black opacity 50% view.
I tried with layer's zPosition but that's doesn't work.
Any idea ?
Thanks
You should add the view in window .
AppDelegate.swift :
class func getDelegate() -> AppDelegate
{
return UIApplication.shared.delegate as! AppDelegate
}
YourViewController.swift
// set frame to full windows size
your_popup_view.frame = AppDelgate.getDelegate().window?.frame;
// add view on the top current window
AppDelgate.getDelegate().window?.addSubview(your_popup_view)
This would be the correct way to do it:
/* Add dialog to main window */
guard let appDelegate = UIApplication.shared.delegate else { fatalError() }
guard let window = appDelegate.window else { fatalError() }
window?.addSubview(self)
window?.bringSubview(toFront: self)
window?.addSubview(**Your view**)
window?.endEditing(true)
You can try this:
class YourView: UIView {
//setup your view...
}
protocol YourViewPresenter {
func showYourView()
func hideYourView()
}
extension YourViewPresenter where Self: UIViewController {
func showYourView() {
let yourView = YourView()
view.addSubview(yourView)
//after adding newly view, need to resize it
yourView.fillSuperview()
}
func hideYourView() {
(view.subviews.filter { $0 is PVInvitePopupView }).first?.removeFromSuperview()
}
}
extension UIView {
func fillSuperView() {
guard let superView = superview else { return }
translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
leadingAnchor.constraint(equalTo: superView.leadingAnchor, constant: 0),
trailingAnchor.constraint(equalTo: superView.trailingAnchor, constant: 0),
topAnchor.constraint(equalTo: superView.topAnchor, constant: 0),
bottomAnchor.constraint(equalTo: superView.bottomAnchor, constant: 0)
])
}
}
And then just conform you TabBarController to this extension:
extension UITabBarController: YourViewPresenter {}
//...
Then, you could call showYourView() somewhere in your viewController, like:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.tabBarController?.showYourView()
}

UIButton exceeding view controller without clipping to its bounds

In my app I'm using KYDrawerController library from here: https://github.com/ykyouhei/KYDrawerController
It works as expected, but I want to add an UIButton on the menu view controller (which is on top when opened), however it's clipped by view bounds. Best way I can explain this is by showing you a screenshot:
And here's how it should look like:
Button now has negative right constraint margin so it's position is correct, but how can I disable clipping?
In the menu view controller, which can you see on the foreground, I've added this code:
self.navigationController?.navigationBar.clipsToBounds = false
self.navigationController?.view.clipsToBounds = false
self.view.clipsToBounds = false
let elDrawer = self.navigationController?.parent as! KYDrawerController
elDrawer.view.clipsToBounds = false
elDrawer.displayingViewController?.view.clipsToBounds = false
elDrawer.drawerViewController?.view.clipsToBounds = false
elDrawer.displayingViewController?.view.clipsToBounds = false
elDrawer.mainViewController.view.clipsToBounds = false
elDrawer.inputViewController?.view.clipsToBounds = false
elDrawer.splitViewController?.view.clipsToBounds = false
As you can see I've tried all possible ways to disable clipping, yet it's still clipped. How can I achieve this?
edit:
Added hierachy view:
I've also tried to run following test:
var view = arrowButton as UIView?
repeat {
view = view?.superview
if let sview = view {
if(sview.clipsToBounds){
print("View \(view) clips to bounds")
break
}
else{
print("View \(view) does not clip to bounds")
}
}
} while (view != nil)
And it prints:
View Optional(>) does not clip to
bounds
So looks like nothing is clipping yet it's clipped.
edit2:
debug view hierarchy:
Yay, I've found the solution:
self.navigationController?.view.subviews[0].clipsToBounds = false
old code is not needed.
UINavigationTransitionView (it's Apple private class) was the one responsible with clipToBounds turned on.
We can't really tell what's going on because you don't show us the view hierarchy.
I suggest you write test code that starts at the button, walking up the superview hierarchy looking for a superview who's' clipsToBounds is true. Something like this:
var view = button as UIView?
repeat {
view = view.superview
if view?.superview.clipsToBounds ?? false == true {
print("View \(view) clips to bounds")
break
} while view != nil
Then you'll know which view is clipping, and you can fix it witout the "shotgun" approach you're using.
EDIT:
I'm not sure why you are getting such strange debug messages. I suggest adding unique tag numbers to all the button's superviews, and then using code like this:
override func viewDidAppear(_ animated: Bool) {
var view = button as UIView?
repeat {
view = view?.superview
if let view = view {
let tag = view.tag
let description = (tag == 0) ? view.debugDescription : "view w tag \(tag)"
if(view.clipsToBounds){
print("View \(description) clips to bounds")
break
}
else{
print("View \(description) does not clip to bounds")
}
}
} while (view != nil)
}
Post ALL the output from that debug code so we can see the entire view hierarchy you're dealing with.

How to choose the right UIGestureRecognizer?

I have view controller which as UIPageViewController inside.
Using pageviewcontroller I can swipt left, right in order to go to other VCs. It works!
So, after I added sideBarMenu. When adding this menu I use this code to add gesture recognizer:
var menuViewController: UIViewController! {
didSet {
self.exitPanGesture = UIPanGestureRecognizer()
self.exitPanGesture.addTarget(self, action:"handleOffstagePan:")
self.sourceViewController.view.addGestureRecognizer(self.exitPanGesture)
}
Here the sourceViewController is my original VC.
The problem is when I try to swipe (in order to close menu), the pageViewController swipe works.
I want to disable pageViewController swipe and enable new swipe function when menu is opened. And do oppositely when menu is closed.
Additional code:
func handleOffstagePan(pan: UIPanGestureRecognizer){
println("dismiss pan gesture recognizer")
let translation = pan.translationInView(pan.view!)
let d = translation.x / CGRectGetWidth(pan.view!.bounds) * -0.5
switch (pan.state) {
case UIGestureRecognizerState.Began:
self.interactive = true
self.menuViewController.performSegueWithIdentifier("dismisMenu", sender: self)
break
case UIGestureRecognizerState.Changed:
self.updateInteractiveTransition(d)
break
default:
self.interactive = false
if d > 0.1 {
self.finishInteractiveTransition()
}else {
isMenuVisible = false
self.cancelInteractiveTransition()
}
}
}
Guys!
SO, the solution is instead of setting PageViewController to the sourceVC of your TransitionManager, set pageContentViewController to the sourceVC. PageContentViewControler is :
func resetToMainPage(index: Int!) {
/* Getting the page View controller */
mainPageViewController = self.storyboard?.instantiateViewControllerWithIdentifier("MainPageViewController") as UIPageViewController
self.mainPageViewController.dataSource = self
self.mainPageViewController.delegate = self
let pageContentViewController = self.viewControllerAtIndex(index)
self.transtionManger.sourceViewController = pageContentViewController // adding swipe to the pageContentViewControlle in order to close menu
self.mainPageViewController.setViewControllers([pageContentViewController!], direction: UIPageViewControllerNavigationDirection.Forward, animated: true, completion: nil)
self.mainPageViewController.view.frame = CGRectMake(0, 102, self.view.frame.width, self.view.frame.height)
self.addChildViewController(mainPageViewController)
self.view.addSubview(mainPageViewController.view)
self.mainPageViewController.didMoveToParentViewController(self)
}
Here I set pageContentVC to the sourveVS of transitionManageClass. NExt how to choose the right GestureRecognizer. By default when you add new gesture recognizer the old one doesnt work. When you disable the new gesture recognizer the old one starts to work! I added new gesture recognizer using code:
var menuViewController: UIViewController! {
didSet {
self.exitPanGesture = UIPanGestureRecognizer()
self.exitPanGesture.addTarget(self, action:"handleOffstagePan:")
// self.exitPanGesture.view?.userInteractionEnabled = false
self.sourceViewController.view.addGestureRecognizer(self.exitPanGesture)
}
}
Before setting menuViewController I set sourceViewController. So, here I am adding new gesture recognizer to my sourceViewController. Next, step is to disable this gesture recognize. When you close the menu disable it using this code:
var presentingP:Bool!{
didSet{
if presentingP == true {
// enable the gesture recognizer only when the view of menucontroller is presented
self.exitPanGesture.view?.userInteractionEnabled = true
}else{
// disable gesture recognizer when menu is not presented
self.exitPanGesture.view?.userInteractionEnabled = false
isMenuVisible = false
}
}
}
PresentingP is boolean value which shows when menu is opened and closed!

Resources