How to display a PopoverViewController from a custom Navigation Bar Right Button? - swift
I created a custom Navigation Bar class as illustrated below, which I used across multiple ViewControllers:
import UIKit
import ChameleonFramework
class CustomUINavigationBar: UINavigationBar {
let navigationBarRightButtonView = UIView()
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
convenience init(rightNavBarButtonTitleForNormalState: String, rightNavBarButtonImageForNormalState: String, rightNavBarButtonImageForHighlightedState: String, rightNavBarButtonTarget: Any?, rightNavBarButtonSelector: Selector, isNavBarTranslucent: Bool, navBarBackgroundColourHexCode: String, navBarBackgroundColourAlphaValue: CGFloat, navBarStyle: UIBarStyle, preferLargeTitles: Bool, navBarDelegate: UINavigationBarDelegate, navBarItemsHexColourCode: String, normalStateNavBarLeftButtonImage: String, highlightedStateNavBarLeftButtonImage: String, navBarLeftButtonTarget: Any?, navBarLeftButtonSelector: Selector, labelTitleText: String, titleLabelFontHexColourCode: String, labelTitleFontSize: CGFloat, labelTitleFontType: String) {
self.init()
addNavBarRightButton(rightNavBarButtonTitleForNormalState: rightNavBarButtonTitleForNormalState, rightNavBarButtonImageForNormalState: rightNavBarButtonImageForNormalState, rightNavBarButtonImageForHighlightedState: rightNavBarButtonImageForHighlightedState, rightNavBarButtonTarget: rightNavBarButtonTarget, rightNavBarButtonSelector: rightNavBarButtonSelector)
addNavBarLeftButton(normalStateNavBarLeftButtonImage: normalStateNavBarLeftButtonImage, highlightedStateNavBarLeftButtonImage: highlightedStateNavBarLeftButtonImage, navBarLeftButtonTarget: navBarLeftButtonTarget, navBarLeftButtonSelector: navBarLeftButtonSelector)
setupNavigationBarEssentials(isNavBarTranslucent: isNavBarTranslucent, navBarBackgroundColourHexCode: navBarBackgroundColourHexCode, navBarBackgroundColourAlphaValue: navBarBackgroundColourAlphaValue, navBarStyle: navBarStyle, preferLargeTitles: preferLargeTitles, navBarDelegate: navBarDelegate, navBarItemsHexColourCode: navBarItemsHexColourCode)
addTitleLabel(labelTitleText: labelTitleText, titleLabelFontHexColourCode: titleLabelFontHexColourCode, labelTitleFontSize: labelTitleFontSize, labelTitleFontType: labelTitleFontType)
}
let customNavigationBarItem = UINavigationItem()
func addTitleLabel(labelTitleText titleText: String, titleLabelFontHexColourCode hexCode: String, labelTitleFontSize fontSize: CGFloat, labelTitleFontType fontType: String) {
let navBarTitle = UILabel(frame: CGRect(x: 0, y: 0, width: frame.width, height: 44))
navBarTitle.text = titleText
navBarTitle.textColor = UIColor(hexString: hexCode)
navBarTitle.textAlignment = .center
navBarTitle.font = UIFont(name: fontType, size: fontSize)
navBarTitle.numberOfLines = 0
navBarTitle.lineBreakMode = .byWordWrapping
customNavigationBarItem.titleView = navBarTitle
}
func addNavBarLeftButton(normalStateNavBarLeftButtonImage: String, highlightedStateNavBarLeftButtonImage: String, navBarLeftButtonTarget: Any?, navBarLeftButtonSelector: Selector) {
let navBarLeftButton: UIButton = {
let button = UIButton()
let normalStateNavBarLeftButtonImage = UIImage(named: normalStateNavBarLeftButtonImage)
let highlightedStateNavBarLeftButtonImage = UIImage(named: highlightedStateNavBarLeftButtonImage)
button.setImage(normalStateNavBarLeftButtonImage, for: .normal)
button.setImage(highlightedStateNavBarLeftButtonImage, for: .highlighted)
button.addTarget(navBarLeftButtonTarget, action: navBarLeftButtonSelector, for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
let navBarLeftView: UIView = {
let view = UIView()
view.addSubview(navBarLeftButton)
NSLayoutConstraint.activate([
navBarLeftButton.topAnchor.constraint(equalTo: view.topAnchor),
navBarLeftButton.rightAnchor.constraint(equalTo: view.rightAnchor),
navBarLeftButton.bottomAnchor.constraint(equalTo: view.bottomAnchor),
navBarLeftButton.leftAnchor.constraint(equalTo: view.leftAnchor)
])
return view
}()
let navBarLeftButtonItem = UIBarButtonItem(customView: navBarLeftView)
customNavigationBarItem.leftBarButtonItem = navBarLeftButtonItem
}
func addNavBarRightButton(rightNavBarButtonTitleForNormalState: String, rightNavBarButtonImageForNormalState: String, rightNavBarButtonImageForHighlightedState: String, rightNavBarButtonTarget: Any?, rightNavBarButtonSelector: Selector) {
rightNavigationBarDropDownButton.setTitle(rightNavBarButtonTitleForNormalState, for: .normal)
rightNavigationBarDropDownButton.setImage(UIImage(named: rightNavBarButtonImageForNormalState), for: .normal)
rightNavigationBarDropDownButton.setTitleColor(.black, for: .normal)
rightNavigationBarDropDownButton.setTitleColor(.blue, for: .highlighted)
rightNavigationBarDropDownButton.addTarget(rightNavBarButtonTarget, action: rightNavBarButtonSelector, for: .touchUpInside)
rightNavigationBarDropDownButton.translatesAutoresizingMaskIntoConstraints = false
navigationBarRightButtonView.addSubview(rightNavigationBarDropDownButton)
NSLayoutConstraint.activate([
rightNavigationBarDropDownButton.topAnchor.constraint(equalTo: navigationBarRightButtonView.topAnchor),
rightNavigationBarDropDownButton.rightAnchor.constraint(equalTo: navigationBarRightButtonView.rightAnchor),
rightNavigationBarDropDownButton.leftAnchor.constraint(equalTo: navigationBarRightButtonView.leftAnchor),
rightNavigationBarDropDownButton.bottomAnchor.constraint(equalTo: navigationBarRightButtonView.bottomAnchor)
])
let navigationBarRightViewitem = UIBarButtonItem(customView: navigationBarRightButtonView)
customNavigationBarItem.rightBarButtonItem = navigationBarRightViewitem
}
func setupNavigationBarEssentials(isNavBarTranslucent: Bool, navBarBackgroundColourHexCode: String, navBarBackgroundColourAlphaValue: CGFloat, navBarStyle: UIBarStyle, preferLargeTitles: Bool, navBarDelegate: UINavigationBarDelegate, navBarItemsHexColourCode: String) {
items = [customNavigationBarItem]
isTranslucent = isNavBarTranslucent
barTintColor = UIColor(hexString: navBarBackgroundColourHexCode, withAlpha: navBarBackgroundColourAlphaValue)
barStyle = navBarStyle
prefersLargeTitles = preferLargeTitles
delegate = navBarDelegate
tintColor = UIColor(hexString: navBarItemsHexColourCode)
translatesAutoresizingMaskIntoConstraints = false
}
}
I then created an instance from the above custom Navigation Bar class into the viewController where I would like the custom Navigation bar to show. Then I tried to present the PopoverViewController whenever the user taps on the NavBarRightButtonItem, however, nothing is showing up, could please someone help me figuring out where did I go wrong, thanks a lot?
import UIKit
class BlueBookUniversalBeamsVC: UIViewController, UINavigationBarDelegate, UIPopoverPresentationControllerDelegate {
lazy var navigationBar = CustomUINavigationBar(rightNavBarButtonTitleForNormalState: "Sort By:", rightNavBarButtonImageForNormalState: "pullDownButton", rightNavBarButtonImageForHighlightedState: "pullUpButton", rightNavBarButtonTarget: self, rightNavBarButtonSelector: #selector(navigationBarRightButtonPressed(sender:)), isNavBarTranslucent: false, navBarBackgroundColourHexCode: "#FFFFFF", navBarBackgroundColourAlphaValue: 1.0, navBarStyle: .black, preferLargeTitles: false, navBarDelegate: self, navBarItemsHexColourCode: "#FF4F40", normalStateNavBarLeftButtonImage: "normalStateBackButton", highlightedStateNavBarLeftButtonImage: "highlightedStateBackButton", navBarLeftButtonTarget: self, navBarLeftButtonSelector: #selector(navigationBarLeftButtonPressed(sender:)), labelTitleText: "Universal Beams (UB)", titleLabelFontHexColourCode: "#000000", labelTitleFontSize: 16, labelTitleFontType: "AppleSDGothicNeo-Light")
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(navigationBar)
}
}
override func viewDidLayoutSubviews() {
setupConstraints()
}
#objc func navigationBarLeftButtonPressed(sender : UIButton) {
let viewControllerToGoTo = BlueBookTabController()
present(viewControllerToGoTo, animated: true, completion: nil)
}
#objc func navigationBarRightButtonPressed(sender : UIButton) {
let button = sender as? UIButton
let buttonFrame = button?.frame ?? CGRect.zero
let popoverContentController = self.storyboard?.instantiateViewController(withIdentifier: "PopoverViewController") as? PopoverViewController
popoverContentController?.modalPresentationStyle = .popover
if let popoverPresentationController = popoverContentController?.popoverPresentationController {
popoverPresentationController.permittedArrowDirections = .up
popoverPresentationController.sourceView = self.view
popoverPresentationController.sourceRect = buttonFrame
popoverPresentationController.delegate = self
if let popoverController = popoverContentController {
present(popoverController, animated: true, completion: nil)
}
}
}
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
func popoverPresentationControllerDidDismissPopover(_ popoverPresentationController: UIPopoverPresentationController) {
}
func popoverPresentationControllerShouldDismissPopover(_ popoverPresentationController: UIPopoverPresentationController) -> Bool {
return true
}
func position(for bar: UIBarPositioning) -> UIBarPosition {
return UIBarPosition.topAttached
}
func setupConstraints() {
NSLayoutConstraint.activate([
navigationBar.leftAnchor.constraint(equalTo: view.leftAnchor),
navigationBar.rightAnchor.constraint(equalTo: view.rightAnchor),
navigationBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
])
}
}
I managed to sort out my problem using the below code, what confused me at the beginning is that I am using a Standalone NavigationBar rather than a NavigationBar Controller:
#objc func navigationBarRightButtonPressed(sender : UIButton) {
let storyboard : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let popOverViewController = storyboard.instantiateViewController(withIdentifier: "PopoverViewController")
popOverViewController.modalPresentationStyle = .popover
let popover = popOverViewController.popoverPresentationController!
popover.delegate = self
popover.permittedArrowDirections = .up
// The sourceView in the below code line represents the view containing the anchor rectangle for the popover:
popover.sourceView = navigationBar.navigationBarRightButtonView
// The sourceRect in the below code line represents The rectangle in the specified view in which to anchor the popover:
popover.sourceRect = navigationBar.navigationBarRightButtonView.bounds
present(popOverViewController, animated: true, completion:nil)
}
Related
inputAccessoryView sizing problem on iPhones without physical home button
inputAccessoryView's background view is falling under its own textField and profile picture imageView. It works fine on regular screen iPhones, but on new iPhones with notches it looks like this: Here's how it looks animated when keyboard appears: Transition animation on becomeFirstResponder() Here's my tableView in which I'm trying to add accessoryView: import UIKit import SDWebImage class CommentsTableViewController: UITableViewController { let viewModel = CommentsViewModel() let postID: String let postCaption: String let postDate: Date let postAuthor: ZoogramUser var keyboardAccessoryView: CommentAccessoryView = { let commentAccessoryView = CommentAccessoryView() return commentAccessoryView }() init(post: UserPost) { self.postID = post.postID self.postCaption = post.caption self.postDate = post.postedDate self.postAuthor = post.author super.init(style: .grouped) self.tableView.register(PostCommentsTableViewCell.self, forCellReuseIdentifier: PostCommentsTableViewCell.identifier) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() self.title = "Comments" keyboardAccessoryView.delegate = self configureKeyboardAccessoryView() viewModel.getComments(for: self.postID) { self.tableView.reloadData() } tableView.backgroundColor = .systemBackground tableView.keyboardDismissMode = .interactive tableView.rowHeight = UITableView.automaticDimension tableView.estimatedRowHeight = 100 tableView.allowsSelection = false tableView.separatorStyle = .none } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) becomeFirstResponder() } override var inputAccessoryView: UIView? { keyboardAccessoryView.heightAnchor.constraint(greaterThanOrEqualToConstant: 60).isActive = true keyboardAccessoryView.backgroundColor = .systemOrange return keyboardAccessoryView } override var canBecomeFirstResponder: Bool { return true } func configureKeyboardAccessoryView() { guard let photoURL = AuthenticationManager.shared.getCurrentUserProfilePhotoURL() else { return } keyboardAccessoryView.userProfilePicture.sd_setImage(with: photoURL) } } And here's code for my CommentAccessoryView which I use to override inputAccessoryView: import UIKit protocol CommentAccessoryViewProtocol { func postButtonTapped(commentText: String) } class CommentAccessoryView: UIView { var delegate: CommentAccessoryViewProtocol? var userProfilePicture: UIImageView = { let imageView = UIImageView() imageView.translatesAutoresizingMaskIntoConstraints = false imageView.contentMode = .scaleAspectFit imageView.clipsToBounds = true imageView.backgroundColor = .secondarySystemBackground imageView.contentMode = .scaleAspectFill return imageView }() var commentTextField: AccessoryViewTextField = { let textField = AccessoryViewTextField() textField.translatesAutoresizingMaskIntoConstraints = false textField.backgroundColor = .systemBackground textField.placeholder = "Enter comment" textField.clipsToBounds = true textField.layer.borderWidth = 1 textField.layer.borderColor = UIColor.placeholderText.cgColor return textField }() var postButton: UIButton = { let button = UIButton(type: .system) button.translatesAutoresizingMaskIntoConstraints = false button.widthAnchor.constraint(equalToConstant: 30).isActive = true button.heightAnchor.constraint(equalToConstant: 30).isActive = true button.clipsToBounds = true button.layer.cornerRadius = 30/2 button.setImage(UIImage(systemName: "arrow.up.circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 35)), for: .normal) button.tintColor = .systemBlue button.addTarget(self, action: #selector(didTapPostButton), for: .touchUpInside) return button }() override init(frame: CGRect) { super.init(frame: frame) setupConstraints() backgroundColor = .systemBackground commentTextField.rightView = postButton commentTextField.rightViewMode = .always autoresizingMask = .flexibleHeight } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() setViewCornerRadius() } func setViewCornerRadius() { userProfilePicture.layer.cornerRadius = userProfilePicture.frame.height / 2 commentTextField.layer.cornerRadius = commentTextField.frame.height / 2 } func setupConstraints() { self.addSubviews(userProfilePicture, commentTextField) NSLayoutConstraint.activate([ userProfilePicture.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10), userProfilePicture.centerYAnchor.constraint(equalTo: self.safeAreaLayoutGuide.centerYAnchor), userProfilePicture.widthAnchor.constraint(equalToConstant: 40), userProfilePicture.heightAnchor.constraint(equalToConstant: 40), commentTextField.leadingAnchor.constraint(equalTo: userProfilePicture.trailingAnchor, constant: 10), commentTextField.centerYAnchor.constraint(equalTo: userProfilePicture.centerYAnchor), commentTextField.heightAnchor.constraint(equalToConstant: 40), commentTextField.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -10), ]) } override var intrinsicContentSize: CGSize { return CGSize.zero } #objc func didTapPostButton() { guard let text = commentTextField.text else { return } commentTextField.resignFirstResponder() delegate?.postButtonTapped(commentText: text) } } I've spent days trying to google a fix for that but nothing helps. There were posts saying they were able to fix something similar by setting customView's bottom constraint to a safe area with the following method: override func didMoveToWindow() { if #available(iOS 11.0, *) { if let window = window { let bottomAnchor = bottomAnchor.constraint(lessThanOrEqualToSystemSpacingBelow: window.safeAreaLayoutGuide.bottomAnchor, multiplier: 1.0) bottomAnchor.isActive = true } } } But when I use it, AutoLayout starts complaining. UPDATE: I did what HangarRash recommended, changed CommentAccessoryView from UIView to UIInputView and centering profileImageView and textField to view itself and not to safe area. Now it's a little bit better, but seems to ignore safe area, inputAccessoryView should be above Home indicator but lies beneath it instead. Looking at last cell in TableView and Scroll indicator, it seems like TableView also isn't aware of inputAccessoryView and goes under it.
How to present image in full screen in UIPageViewController?
I'm trying to present full images in UIPageViewController. Each image is in separate view controller. I set contentMode to scaleAspectFill. The problem is that when I scroll back, two images merge like this https://i.imgur.com/CSgJLWq.png. I also tried to give width to every image but nothing changes private lazy var imageView: UIImageView = { let imageView = UIImageView() imageView.image = UIImage(named: "onboarding-concert") imageView.contentMode = .scaleAspectFill //imageView.frame.size.width = 400 return imageView }() private func setUI() { view.addSubview(imageView) imageView.snp.makeConstraints { make in make.left.equalToSuperview() make.right.equalToSuperview() make.top.equalToSuperview() make.bottom.equalToSuperview() } view.addSubview(label) label.snp.makeConstraints { make in make.top.equalToSuperview().inset(90) make.left.equalToSuperview().inset(40) } } UIPageViewController code class OnboardingViewController: UIPageViewController { var pages = [UIViewController]() let pageControl = UIPageControl() let initialPage = 0 var pageControlBottomAnchor: NSLayoutConstraint? override func viewDidLoad() { setup() style() layout() } func setup() { dataSource = self delegate = self pageControl.addTarget(self, action: #selector(pageControlTapped(_:)), for: .valueChanged) let page1 = FirstPageViewController() let page2 = SecondPageViewController() let page3 = ThirdPageViewController() pages.append(page1) pages.append(page2) pages.append(page3) setViewControllers([pages[initialPage]], direction: .forward, animated: true, completion: nil) } func style() { pageControl.translatesAutoresizingMaskIntoConstraints = false pageControl.currentPageIndicatorTintColor = .black pageControl.pageIndicatorTintColor = .systemGray2 pageControl.numberOfPages = pages.count pageControl.currentPage = initialPage pageControl.isUserInteractionEnabled = false } func layout() { view.addSubview(pageControl) pageControl.snp.makeConstraints { make in make.width.equalToSuperview() make.height.equalTo(20) make.centerX.equalToSuperview() } pageControlBottomAnchor = view.bottomAnchor.constraint(equalToSystemSpacingBelow: pageControl.bottomAnchor, multiplier: 2) pageControlBottomAnchor?.isActive = true } #objc func pageControlTapped(_ sender: UIPageControl) { setViewControllers([pages[sender.currentPage]], direction: .forward, animated: true, completion: nil) } }
Problem with delegates removing annotation
I have two screens. The first one (firstViewController) has a mapView with a UITapGestureRecognizer. When the user taps the screen, an annotations is added to the map and the second screen (secondViewController) is presented. When the user dismisses the secondViewController and comes back to the first one, the annotation should be removed. I know I have to use delegation, but I just can't make it to work. This is the code I have now: class firstViewController: UIViewController, AnnotationDelegate { let mapView = MKMapView() var temporaryPinArray = [MKPointAnnotation]() override func viewDidLoad() { super.viewDidLoad() view.addSubview(mapView let gesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:))) mapView.addGestureRecognizer(gesture) secondVC.annotationDelegate = self } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() mapView.frame = view.bounds } #objc func handleTap(_ gestureReconizer: UILongPressGestureRecognizer) { let location = gestureReconizer.location(in: mapView) let coordinates = mapView.convert(location, toCoordinateFrom: mapView) mapView.removeAnnotations(mapView.annotations) let pin = MKPointAnnotation() pin.coordinate = coordinates temporaryPinArray.removeAll() temporaryPinArray.append(pin) mapView.addAnnotations(temporaryPinArray) // Present secondViewController let secondVC = SecondViewController() panel.set(contentViewController: secondVC) panel.addPanel(toParent: self) } func didRemoveAnnotation(annotation: MKPointAnnotation) { mapView.removeAnnotation(annotation) } } Second View Controller protocol AnnotationDelegate { func didRemoveAnnotation(annotation: [MKPointAnnotation]) } class SecondViewController: UIViewController { var annotationDelegate: AnnotationDelegate! let mainVC = firstViewController() let closeButton: UIButton = { let button = UIButton() button.backgroundColor = .grey button.layer.cornerRadius = 15 return button }() override func viewDidLoad() { super.viewDidLoad() view.addSubview(closeButton) closeButton.addTarget(self, action: #selector(dismissPanel), for: .touchUpInside) } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() closeButton.frame = CGRect(x: view.frame.width-50, y: 10, width: 30, height: 30) } #objc func dismissPanel() { self.dismiss(animated: true, completion: nil) annotationDelegate.didRemoveAnnotation(annotation: mainVC.temporaryPinArray) } } Thank you so much for your help!
You created a new instance of firstViewController inside SecondViewController. This instance is unrelated to the actual first one: let mainVC = firstViewController() This means that temporaryPinArray is different as well. So instead of passing in this unrelated array... #objc func dismissPanel() { self.dismiss(animated: true, completion: nil) annotationDelegate.didRemoveAnnotation(annotation: mainVC.temporaryPinArray) } Just change the function to take no parameters instead: protocol AnnotationDelegate { func didRemoveAnnotation() /// no parameters } #objc func dismissPanel() { self.dismiss(animated: true, completion: nil) annotationDelegate.didRemoveAnnotation() /// no parameters } And inside firstViewController's didRemoveAnnotation, reference the actual temporaryPinArray. func didRemoveAnnotation() { mapView.removeAnnotations(temporaryPinArray) /// the current array }
In keeping with the recent questions on closures used to pass data between VCs, how would I do the same when using a containerVC within the rootVC?
I saw a recent bountied question (can find the link if you wish to see it) about using closures to pass data between VCs where one VC was embedded in a navigation controller. While the use of a closure there was fairly easy since there was a direct point of contact between the two VCs (in the form a segue), I have been wondering how the same would work if this was not the case. As an example, consider the following set up (similar to the OG question that inspired this post): RootVC, which has a counter UILabel A subContainer VC which takes up the lower half of RootVC, which has a button, pressing which should increment the UILabel on RootVC by one. I have prepared the code as follows (with some code taken from the OG question): RootVC: class RootVC: UIViewController { var tappedCount: Int = 0 let pagingContainer: UIView = { let view = UIView() view.backgroundColor = .white view.translatesAutoresizingMaskIntoConstraints = false return view }() lazy var label: UILabel = { let label = UILabel() label.text = "\(tappedCount)" label.textAlignment = .center label.font = UIFont(name: "Copperplate", size: 90) label.translatesAutoresizingMaskIntoConstraints = false return label }() override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white view.addSubview(label) view.addSubview(pagingContainer) pagingContainer.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true pagingContainer.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 1).isActive = true pagingContainer.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true pagingContainer.heightAnchor.constraint(equalToConstant: 500).isActive = true let pageController = PageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal) addChild(pageController) pageController.didMove(toParent: self) pageController.view.translatesAutoresizingMaskIntoConstraints = false pagingContainer.addSubview(pageController.view) pageController.view.heightAnchor.constraint(equalTo: pagingContainer.heightAnchor, multiplier: 1).isActive = true pageController.view.widthAnchor.constraint(equalTo: pagingContainer.widthAnchor, multiplier: 1).isActive = true pageController.view.topAnchor.constraint(equalTo: pagingContainer.topAnchor).isActive = true pageController.view.bottomAnchor.constraint(equalTo: pagingContainer.bottomAnchor).isActive = true pageController.view.leadingAnchor.constraint(equalTo: pagingContainer.leadingAnchor).isActive = true pageController.view.trailingAnchor.constraint(equalTo: pagingContainer.trailingAnchor).isActive = true label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true label.bottomAnchor.constraint(equalTo: pagingContainer.topAnchor).isActive = true } } SubContainerVC: class SubContainerVC: UIViewController { var callback : (() -> Void)? let button: UIButton = { let button = UIButton() button.setTitle("Button!", for: .normal) button.translatesAutoresizingMaskIntoConstraints = false button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside) button.backgroundColor = .green return button }() #objc func buttonPressed(_ sender: UIButton) { print("Hello") //Pressing this button should increment the label on RootVC by one. } override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .systemBlue view.addSubview(button) button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true } } And the PageViewController swift file: class PageViewController: UIPageViewController { lazy var subViewControllers:[UIViewController] = { return [SubContainerVC()] }() init(transitionStyle style: UIPageViewController.TransitionStyle, navigationOrientation: UIPageViewController.NavigationOrientation, options: [String : Any]? = nil) { super.init(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() dataSource = self delegate = self setViewControllerFromIndex(index: 0) } func setViewControllerFromIndex(index:Int) { self.setViewControllers([subViewControllers[index]], direction: UIPageViewController.NavigationDirection.forward, animated: true, completion: nil) } } extension PageViewController: UIPageViewControllerDelegate, UIPageViewControllerDataSource { func presentationCount(for pageViewController: UIPageViewController) -> Int { return subViewControllers.count } func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { let currentIndex:Int = subViewControllers.firstIndex(of: viewController) ?? 0 if currentIndex <= 0 { return nil } return subViewControllers[currentIndex-1] } func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { let currentIndex:Int = subViewControllers.firstIndex(of: viewController) ?? 0 if currentIndex >= subViewControllers.count-1 { return nil } return subViewControllers[currentIndex+1] } }
You can inject the closure downstream to SubContainerVC, this will result in the closure execution coming up upstream. Something along the lines (kept only the relevant VC code): class SubContainerVC { var buttonCallback: () -> Void = { } #objc func buttonPressed(_ sender: UIButton) { print("Hello") buttonCallback() } } class PageViewController: UIViewController { // Note that you don't need the extra closure call for lazy vars lazy var subViewControllers = [SubContainerVC()] { didSet { // just in case the controllers might change later on subViewControllers.forEach { $0.buttonCallback = buttonCallback } } } var buttonCallback: () -> Void = { } { didSet { subViewControllers.forEach { $0.buttonCallback = buttonCallback } } } } class RootVC: UIViewController { var tappedCount: Int = 0 { didSet { label.text = "\(tappedCount)" } } override func viewDidLoad() { super.viewDidLoad() let pageController = PageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal) // this will trigger the `didSet` from PageViewController, resulting // in the callback being propagated downstream pageController.buttonCallback = { self.tappedCount += 1 } } }
UIVisualEffectView creating unwanted shadow while presenting new view
In my custom presentation transition I've created a new view controller which will pre presented on top of the current active view controller (see screenshot). Somehow there's a shadow behind the blue view controller and I have no idea where it's coming from. Is there a way to stop getting that shadow? The project is completely empty and has only 2 empty view controllers. This is the code I'm using: class ViewController: UIViewController { let transitionDelegate = TransitionManager() override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .yellowColor() let button = UIButton(type: .System) button.frame = CGRectMake(10, 10, 50, 50) button.addTarget(self, action: "test:", forControlEvents: .TouchUpInside) button.backgroundColor = UIColor.redColor() view.addSubview(button) } func test(sender: UIButton) { let destination = UIViewController() destination.view.backgroundColor = .blueColor() destination.transitioningDelegate = transitionDelegate destination.modalPresentationStyle = .Custom presentViewController(destination, animated: true, completion: nil) } } The code for presenting the view: class PresentingTransition: NSObject, UIViewControllerAnimatedTransitioning { func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval { return 0.3 } func animateTransition(transitionContext: UIViewControllerContextTransitioning) { let presented = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)! let container = transitionContext.containerView()! let durations = transitionDuration(transitionContext) presented.view.alpha = 0 container.addSubview(presented.view) UIView.animateWithDuration(durations, animations: { presented.view.alpha = 1 }) { transitionContext.completeTransition($0) } } } The code for handling the presenting view controller: class PresentationController: UIPresentationController { var background: UIView! override init(presentedViewController: UIViewController, presentingViewController: UIViewController) { super.init(presentedViewController: presentedViewController, presentingViewController: presentingViewController) prepareBackground() } func prepareBackground() { self.background = UIView(frame: presentingViewController.view.bounds) let blur = UIVisualEffectView(effect: UIBlurEffect(style: .Light)) blur.frame = background.bounds blur.autoresizingMask = [.FlexibleHeight, .FlexibleWidth] background.addSubview(blur) let tapRecognizer = UITapGestureRecognizer(target: self, action: "backgroundTapped:") background.addGestureRecognizer(tapRecognizer) } func backgroundTapped(tapRecognizer: UITapGestureRecognizer) { presentingViewController.dismissViewControllerAnimated(true, completion: nil) } override func presentationTransitionWillBegin() { let container = containerView! background.frame = container.bounds background.alpha = 0.0 container.insertSubview(background, atIndex: 0) presentedViewController.transitionCoordinator()?.animateAlongsideTransition({ _ in self.background.alpha = 1.0 }, completion: nil) } override func dismissalTransitionWillBegin() { presentedViewController.transitionCoordinator()?.animateAlongsideTransition({ _ in self.background.alpha = 0.0 }, completion: nil) } override func frameOfPresentedViewInContainerView() -> CGRect { return containerView!.bounds.insetBy(dx: 100, dy: 100) } override func containerViewWillLayoutSubviews() { background.frame = containerView!.bounds presentedView()!.frame = frameOfPresentedViewInContainerView() } }