Here is what I am trying to do:
Note: The screenshot is taken from an earlier version of iOS
What I have been able to achieve:
Code:
override func viewWillAppear(animated: Bool) {
// Creates image of the Button
let imageCameraButton: UIImage! = UIImage(named: "cameraIcon")
// Creates a Button
let cameraButton = UIButton(type: .Custom)
// Sets width and height to the Button
cameraButton.frame = CGRectMake(0.0, 0.0, imageCameraButton.size.width, imageCameraButton.size.height);
// Sets image to the Button
cameraButton.setBackgroundImage(imageCameraButton, forState: .Normal)
// Sets the center of the Button to the center of the TabBar
cameraButton.center = self.tabBar.center
// Sets an action to the Button
cameraButton.addTarget(self, action: "doSomething", forControlEvents: .TouchUpInside)
// Adds the Button to the view
self.view.addSubview(cameraButton)
}
I did try to create a rounded button in the normal way, but this was the result:
Code Snippet for rounded button:
//Creation of Ronded Button
cameraButton.layer.cornerRadius = cameraButton.frame.size.width/2
cameraButton.clipsToBounds = true
Solution
You need to subclass UITabBarController and then add the button above TabBar's view. A button action should trigger UITabBarController tab change by setting selectedIndex.
Code
The code below only is a simple approach, however for a full supporting iPhone (including X-Series)/iPad version you can check the full repository here: EBRoundedTabBarController
class CustomTabBarController: UITabBarController {
// MARK: - View lifecycle
override func viewDidLoad() {
super.viewDidLoad()
let controller1 = UIViewController()
controller1.tabBarItem = UITabBarItem(tabBarSystemItem: .contacts, tag: 1)
let nav1 = UINavigationController(rootViewController: controller1)
let controller2 = UIViewController()
controller2.tabBarItem = UITabBarItem(tabBarSystemItem: .contacts, tag: 2)
let nav2 = UINavigationController(rootViewController: controller2)
let controller3 = UIViewController()
let nav3 = UINavigationController(rootViewController: controller3)
nav3.title = ""
let controller4 = UIViewController()
controller4.tabBarItem = UITabBarItem(tabBarSystemItem: .contacts, tag: 4)
let nav4 = UINavigationController(rootViewController: controller4)
let controller5 = UIViewController()
controller5.tabBarItem = UITabBarItem(tabBarSystemItem: .contacts, tag: 5)
let nav5 = UINavigationController(rootViewController: controller5)
viewControllers = [nav1, nav2, nav3, nav4, nav5]
setupMiddleButton()
}
// MARK: - Setups
func setupMiddleButton() {
let menuButton = UIButton(frame: CGRect(x: 0, y: 0, width: 64, height: 64))
var menuButtonFrame = menuButton.frame
menuButtonFrame.origin.y = view.bounds.height - menuButtonFrame.height
menuButtonFrame.origin.x = view.bounds.width/2 - menuButtonFrame.size.width/2
menuButton.frame = menuButtonFrame
menuButton.backgroundColor = UIColor.red
menuButton.layer.cornerRadius = menuButtonFrame.height/2
view.addSubview(menuButton)
menuButton.setImage(UIImage(named: "example"), for: .normal)
menuButton.addTarget(self, action: #selector(menuButtonAction(sender:)), for: .touchUpInside)
view.layoutIfNeeded()
}
// MARK: - Actions
#objc private func menuButtonAction(sender: UIButton) {
selectedIndex = 2
}
}
Output
Swift 3 Solution
With a slight adjustment to EricB's solution to have this work for Swift 3, the menuButton.addTarget() method needs to have it's selector syntax changed a bit.
Here is the new menuButton.addTarget() function:
menuButton.addTarget(self, action: #selector(MyTabBarController.menuButtonAction), for: UIControlEvents.touchUpInside)
When defining my TabBarController class, I also add a UITabBarControllerDelegate and placed all of the that in the
override func viewDidAppear(_ animated: Bool) { ... }
For extra clarity, the full code is:
Full Code Solution
import UIKit
class MyTabBarController: UITabBarController, UITabBarControllerDelegate {
// View Did Load
override func viewDidLoad() {
super.viewDidLoad()
}
// Tab Bar Specific Code
override func viewDidAppear(_ animated: Bool) {
let controller1 = UIViewController(self.view.backgroundColor = UIColor.white)
controller1.tabBarItem = UITabBarItem(tabBarSystemItem: UITabBarSystemItem.contacts, tag: 1)
let nav1 = UINavigationController(rootViewController: controller1)
let controller2 = UIViewController()
controller2.tabBarItem = UITabBarItem(tabBarSystemItem: UITabBarSystemItem.contacts, tag: 2)
let nav2 = UINavigationController(rootViewController: controller2)
let controller3 = UIViewController()
let nav3 = UINavigationController(rootViewController: controller3)
nav3.title = ""
let controller4 = UIViewController()
controller4.tabBarItem = UITabBarItem(tabBarSystemItem: UITabBarSystemItem.contacts, tag: 4)
let nav4 = UINavigationController(rootViewController: controller4)
let controller5 = UIViewController()
controller5.tabBarItem = UITabBarItem(tabBarSystemItem: UITabBarSystemItem.contacts, tag: 5)
let nav5 = UINavigationController(rootViewController: controller5)
self.viewControllers = [nav1, nav2, nav3, nav4, nav5]
self.setupMiddleButton()
}
// TabBarButton – Setup Middle Button
func setupMiddleButton() {
let menuButton = UIButton(frame: CGRect(x: 0, y: 0, width: 64, height: 64))
var menuButtonFrame = menuButton.frame
menuButtonFrame.origin.y = self.view.bounds.height - menuButtonFrame.height
menuButtonFrame.origin.x = self.view.bounds.width / 2 - menuButtonFrame.size.width / 2
menuButton.frame = menuButtonFrame
menuButton.backgroundColor = UIColor.red
menuButton.layer.cornerRadius = menuButtonFrame.height/2
self.view.addSubview(menuButton)
menuButton.setImage(UIImage(named: "example"), for: UIControlState.normal)
menuButton.addTarget(self, action: #selector(MyTabBarController.menuButtonAction), for: UIControlEvents.touchUpInside)
self.view.layoutIfNeeded()
}
// Menu Button Touch Action
func menuButtonAction(sender: UIButton) {
self.selectedIndex = 2
// console print to verify the button works
print("Middle Button was just pressed!")
}
}
This is the customTabbarcontroller class which is the subclass of UITabbarcontroller. It's the same idea as given by #EridB. But in his code #Raymond26's issue wasn't solved. So, posting a complete solution written in Swift 3.0
protocol CustomTabBarControllerDelegate
{
func customTabBarControllerDelegate_CenterButtonTapped(tabBarController:CustomTabBarController, button:UIButton, buttonState:Bool);
}
class CustomTabBarController: UITabBarController, UITabBarControllerDelegate
{
var customTabBarControllerDelegate:CustomTabBarControllerDelegate?;
var centerButton:UIButton!;
private var centerButtonTappedOnce:Bool = false;
override func viewDidLayoutSubviews()
{
super.viewDidLayoutSubviews();
self.bringcenterButtonToFront();
}
override func viewDidLoad()
{
super.viewDidLoad()
self.delegate = self;
self.tabBar.barTintColor = UIColor.red;
let dashboardVC = DashboardViewController()
dashboardVC.tabBarItem = UITabBarItem(tabBarSystemItem: .topRated, tag: 1)
let nav1 = UINavigationController(rootViewController: dashboardVC)
let myFriendsVC = MyFriendsViewController()
myFriendsVC.tabBarItem = UITabBarItem(tabBarSystemItem: .featured, tag: 2)
let nav2 = UINavigationController(rootViewController: myFriendsVC)
let controller3 = UIViewController()
let nav3 = UINavigationController(rootViewController: controller3)
nav3.title = ""
let locatorsVC = LocatorsViewController()
locatorsVC.tabBarItem = UITabBarItem(tabBarSystemItem: .downloads, tag: 4)
let nav4 = UINavigationController(rootViewController: locatorsVC)
let getDirectionsVC = GetDirectionsViewController()
getDirectionsVC.tabBarItem = UITabBarItem(tabBarSystemItem: .history, tag: 5)
let nav5 = UINavigationController(rootViewController: getDirectionsVC)
viewControllers = [nav1, nav2, nav3, nav4, nav5]
self.setupMiddleButton()
}
// MARK: - TabbarDelegate Methods
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController)
{
switch viewController
{
case is DashboardViewController:
self.showCenterButton()
case is MyFriendsViewController:
self.showCenterButton()
case is GetDirectionsViewController:
self.showCenterButton()
case is LocatorsViewController:
self.showCenterButton()
default:
self.showCenterButton()
}
}
// MARK: - Internal Methods
#objc private func centerButtonAction(sender: UIButton)
{
// selectedIndex = 2
if(!centerButtonTappedOnce)
{
centerButtonTappedOnce=true;
centerButton.setImage(UIImage(named: "ic_bullseye_white"), for: .normal)
}
else
{
centerButtonTappedOnce=false;
centerButton.setImage(UIImage(named: "ic_bullseye_red"), for: .normal)
}
customTabBarControllerDelegate?.customTabBarControllerDelegate_CenterButtonTapped(tabBarController: self,
button: centerButton,
buttonState: centerButtonTappedOnce);
}
func hideCenterButton()
{
centerButton.isHidden = true;
}
func showCenterButton()
{
centerButton.isHidden = false;
self.bringcenterButtonToFront();
}
// MARK: - Private methods
private func setupMiddleButton()
{
centerButton = UIButton(frame: CGRect(x: 0, y: 0, width: 64, height: 64))
var centerButtonFrame = centerButton.frame
centerButtonFrame.origin.y = view.bounds.height - centerButtonFrame.height
centerButtonFrame.origin.x = view.bounds.width/2 - centerButtonFrame.size.width/2
centerButton.frame = centerButtonFrame
centerButton.backgroundColor = UIColor.red
centerButton.layer.cornerRadius = centerButtonFrame.height/2
view.addSubview(centerButton)
centerButton.setImage(UIImage(named: "ic_bullseye_red"), for: .normal)
centerButton.setImage(UIImage(named: "ic_bullseye_white"), for: .highlighted)
centerButton.addTarget(self, action: #selector(centerButtonAction(sender:)), for: .touchUpInside)
view.layoutIfNeeded()
}
private func bringcenterButtonToFront()
{
print("bringcenterButtonToFront called...")
self.view.bringSubview(toFront: self.centerButton);
}
}
This is the DashboardViewController for complete reference:
class DashboardViewController: BaseViewController, CustomTabBarControllerDelegate
{
override func viewDidLoad()
{
super.viewDidLoad()
(self.tabBarController as! CustomTabBarController).customTabBarControllerDelegate = self;
}
override func viewWillAppear(_ animated: Bool)
{
super.viewWillAppear(animated);
(self.tabBarController as! CustomTabBarController).showCenterButton();
}
override func viewWillDisappear(_ animated: Bool)
{
super.viewWillDisappear(animated);
self.hidesBottomBarWhenPushed = false;
(self.tabBarController as! CustomTabBarController).hideCenterButton();
}
override func viewWillLayoutSubviews()
{
super.viewWillLayoutSubviews();
if(!isUISetUpDone)
{
self.view.backgroundColor = UIColor.lightGray
self.title = "DASHBOARD"
self.prepareAndAddViews();
self.isUISetUpDone = true;
}
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
}
//MARK: CustomTabBarControllerDelegate Methods
func customTabBarControllerDelegate_CenterButtonTapped(tabBarController: CustomTabBarController, button: UIButton, buttonState: Bool)
{
print("isDrive ON : \(buttonState)");
}
//MARK: Internal Methods
func menuButtonTapped()
{
let myFriendsVC = MyFriendsViewController()
myFriendsVC.hidesBottomBarWhenPushed = true;
self.navigationController!.pushViewController(myFriendsVC, animated: true);
}
//MARK: Private Methods
private func prepareAndAddViews()
{
let menuButton = UIButton(frame: CGRect(x: 100, y: 100, width: 100, height: 50))
menuButton.titleLabel?.text = "Push"
menuButton.titleLabel?.textColor = UIColor.white
menuButton.backgroundColor = UIColor.red;
menuButton.addTarget(self, action: #selector(DashboardViewController.menuButtonTapped), for: .touchUpInside)
self.view.addSubview(menuButton);
}
}
with StoryBoard:
Click the tab bar button within the view controller of the particular tab bar item you want to make prominent,
Remove the text, just set the image inset top to -25 of the tab bar button.
Check Like Below image
Related
I have been implemented UITabBarController programmatically. Functionality works fine but the UITabBarController is not fitting inside the screen.
here is my code:
class ViewController: UIViewController {
let tabBarCnt = UITabBarController()
override func viewDidLoad() {
super.viewDidLoad()
tabBarCnt.tabBar.tintColor = UIColor.black
createTabBarController()
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
addHeightConstraintToTabbar()
}
func addHeightConstraintToTabbar() -> Void {
let heightConstant:CGFloat = self.view.safeAreaInsets.bottom + 49.0
tabBarCnt.tabBar.heightAnchor.constraint(equalToConstant: heightConstant).isActive = true
}
func createTabBarController() {
let firstVc = UIViewController()
firstVc.title = "First"
firstVc.view.backgroundColor = UIColor.red
firstVc.tabBarItem = UITabBarItem.init(title: "Home", image: UIImage(named: "HomeTab"), tag: 0)
let secondVc = UIViewController()
secondVc.title = "Second"
secondVc.view.backgroundColor = UIColor.green
secondVc.tabBarItem = UITabBarItem.init(title: "Location", image: UIImage(named: "Location"), tag: 1)
let controllerArray = [firstVc, secondVc]
tabBarCnt.viewControllers = controllerArray.map{ UINavigationController.init(rootViewController: $0)}
self.view.addSubview(tabBarCnt.view)
}
}
Result screenshot
Add this line:
tabBarCnt.additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
I am struggling to get inputAccessoryView to show up in my UICollectionViewController. Ok here is a simple explanation of what I have.
A UIPageViewController with 3 ViewControllers as pages - so that I can scroll horizontally between them
PageViewController also has segmented view embedded in navigation bar. Have programmed it in a way where when I press a segment, the PageViewController scrolls to the relevant Viewcontroller
One of the ViewControllers in the PageViewController is my ChatViewController
ChatViewController is a UICollectionViewController
Now ignoring this PageViewController, if I simply present the ChatViewController modally, the following code gets called and everything works as expected. I can see the keyboard, type into the input accessory textview and dismiss it.
Code present in ChatViewController
override var inputAccessoryView: UIView? { //IN ChatViewController
get {
if self.typeReply == true{
return viewForReplyInputAccessory
} else {
return viewForInputAccessory
}
}
}
override var canBecomeFirstResponder: Bool {
return true
}
lazy var viewForInputAccessory: KeyboardView = {
let civ = KeyboardView(frame: .init(x: 0, y: 0, width: view.frame.width, height: 50))
civ.sendButton.isUserInteractionEnabled = true
let gcSend = UITapGestureRecognizer(target: self, action: #selector(handleSend))
civ.addGestureRecognizer(gcSend)
return civ
}()
lazy var viewForReplyInputAccessory: KeyboardReplyView = {
let civ = KeyboardReplyView(frame: .init(x: 0, y: 0, width: view.frame.width, height: 114))
civ.sendButton.isUserInteractionEnabled = true
let gcSend = UITapGestureRecognizer(target: self, action: #selector(handleSend))
civ.addGestureRecognizer(gcSend)
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleReplyMessageClose))
civ.closeImageView.addGestureRecognizer(gestureRecognizer)
return civ
}()
The Problem
When this ChatViewController comes nested in this UIPageViewController, for some reason, inputAccessoryView is not shown. override var canBecomeFirstResponder: Bool is not called. Again it works if I simply present the ChatViewController modally without nesting it anywhere. Am I doing something wrong?
For more clarity. Here is my Hierarchy:
UINavigationController (has embedded segmented view) -> UIPageViewController -> [VC1, ChatVC, VC3]
Here is the code for my UIPageViewController
class JobsContainerScreenController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
var controllers = [UIViewController]()
var segmentedControl: UISegmentedControl!
var currentSegment: Int = 0
let jobsDetailScreenController = JobsDetailScreenController()
let jobsChatScreenController = ChatController(collectionViewLayout: UICollectionViewFlowLayout())
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
let index = controllers.firstIndex(where: {$0 == viewController}) ?? 0
if index != 1 {
self.currentSegment = index
}
if index == 0{
self.segmentedControl.selectedSegmentIndex = index
return nil
}
self.segmentedControl.selectedSegmentIndex = index
return controllers[index - 1]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
let index = controllers.firstIndex(where: {$0 == viewController}) ?? 0
if index != 1 {
self.currentSegment = index
}
if index == controllers.count - 1{
self.segmentedControl.selectedSegmentIndex = index
return nil
}
self.segmentedControl.selectedSegmentIndex = index
return controllers[index + 1]
}
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
handleSegment()
}
fileprivate func setupViews() {
view.backgroundColor = .white
self.overrideUserInterfaceStyle = .light
dataSource = self
delegate = self
view.isUserInteractionEnabled = true
let navBarAppearance = UINavigationBarAppearance()
navBarAppearance.configureWithOpaqueBackground()
navBarAppearance.backgroundColor = .white
navBarAppearance.shadowColor = .clear
let backImage = UIImage(systemName: ImageBackArrow)?.withRenderingMode(.alwaysTemplate)
let leftBarButtonItem = UIBarButtonItem(image: backImage, style: .plain, target: self, action: #selector(handleBack))
navigationItem.leftBarButtonItem = leftBarButtonItem
navigationItem.leftBarButtonItem?.tintColor = ColorBlackAlpha
navigationController?.navigationBar.standardAppearance = navBarAppearance
navigationController?.navigationBar.scrollEdgeAppearance = navBarAppearance
self.segmentedControl = UISegmentedControl(items: ["Activity", "Chat", "Contract"])
self.segmentedControl.sizeToFit()
self.segmentedControl.backgroundColor = UIColor.black.withAlphaComponent(0.01)
self.segmentedControl.selectedSegmentTintColor = UIColor.white
self.segmentedControl.selectedSegmentIndex = 0
self.segmentedControl.setTitleTextAttributes([NSAttributedString.Key.font : UIFont(name: FontPromptBold, size: 14)!, NSAttributedString.Key.foregroundColor: ColorBlackLow], for: .normal)
self.segmentedControl.setTitleTextAttributes([NSAttributedString.Key.font : UIFont(name: FontPromptBold, size: 14)!, NSAttributedString.Key.foregroundColor: ColorDarkGreen], for: .selected)
self.segmentedControl.addTarget(self, action: #selector(handleSegment), for: .valueChanged)
self.navigationItem.titleView = segmentedControl
jobsDetailScreenController.job = self.job
jobsChatScreenController.job = self.job
let jobsContractScreenController = JobsContractScreenController()
controllers = [jobsDetailScreenController, jobsChatScreenController, jobsContractScreenController]
setViewControllers([controllers.first!], direction: .forward, animated: false, completion: nil)
}
#objc func handleSegment() {
if self.segmentedControl.selectedSegmentIndex == 0 {
currentSegment = 0
setViewControllers([controllers.first!], direction: .reverse, animated: true, completion: nil)
} else if segmentedControl.selectedSegmentIndex == 1 {
if currentSegment == 0 {
setViewControllers([controllers[1]], direction: .forward, animated: true, completion: nil)
} else {
setViewControllers([controllers[1]], direction: .reverse, animated: true, completion: nil)
}
} else {
currentSegment = 2
setViewControllers([controllers[2]], direction: .forward, animated: true, completion: nil)
}
}
}
I have a parent view controller and a child view controller that takes up a portion of the parent's main view:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let childVC = ChildVC()
addChild(childVC)
childVC.view.frame = CGRect(origin: CGPoint(x: 100, y: 100), size: CGSize(width: 200, height: 200))
view.addSubview(childVC.view)
childVC.didMove(toParent: self)
}
}
class ChildVC: UIViewController {
override func loadView() {
let v = UIView()
v.backgroundColor = .cyan
view = v
}
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton(type: .system)
button.setTitle("Present", for: .normal)
button.addTarget(self, action: #selector(pressed), for: .touchUpInside)
button.sizeToFit()
view.addSubview(button)
}
#objc func pressed() {
self.definesPresentationContext = true
self.providesPresentationContextTransitionStyle = true
self.modalTransitionStyle = .crossDissolve
let pvc = PresentedVC()
pvc.modalPresentationStyle = .currentContext
self.present(pvc, animated: true, completion: nil)
}
}
class PresentedVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .yellow
let button = UIButton(type: .system)
button.setTitle("Dismiss", for: .normal)
button.addTarget(self, action: #selector(pressed), for: .touchUpInside)
button.sizeToFit()
view.addSubview(button)
}
#objc func pressed() {
self.dismiss(animated: true, completion: nil)
}
}
When I present a view controller using the currentContext style, it presents the new view controller as it should, covering only the child view controller's view:
However, when I dismiss it, the size of the ChildVC's main view takes up the entire screen:
When I log the superview of the ChildVC's main view, it's still a subview of the parent view controller's main view so I'm not sure why this is happening.
Just change your presentationStyle to .overCurrentContext like this:
#objc func pressed() {
self.definesPresentationContext = true
self.providesPresentationContextTransitionStyle = true
self.modalTransitionStyle = .crossDissolve
let pvc = PresentedVC()
pvc.modalPresentationStyle = .overCurrentContext // <-- here
pvc.modalTransitionStyle = .crossDissolve // <-- and here
self.present(pvc, animated: true, completion: nil)
}
It will prevent the unwanted upscaling.
I have the scheme: UITabBarViewController (with 3 tabs).
In all that tabs I don't want to show navigation menu on top.
And from the first tab, I want to push another view controller from button click that will have "back" button (and top toolbar with "cancel")
I tried some ways - in storyboard with push segue - no back button.
Probably because i don't have navigation view controller, so my navigation stack is empty.
Programmatically:
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let nextViewController = storyBoard.instantiateViewController(withIdentifier: "AddCoinTableViewController") as! AddCoinTableViewController
self.present(nextViewController, animated:true, completion:nil)
If I embed tabs in navigation controller, then I have top toolbar (which I don't want).
Any ideas how to make it?
You can't achieve navigation functionality without using UINavigationController. I mean you have to do all animation kind of stuff on your own, and I think that's not a good idea. Rather than that, you can use UINavigationController, and if you don't want to show navigationBar at some viewController, than do as follows for those view controllers.
override func viewWillApear() {
super.viewDidLoad()
self.navigationController?.isNavigationBarHidden = true
}
override func viewWillDisappear(animated: Bool) {
self.navigationController?.isNavigationBarHidden = false
}
You can embed the navigation controller at your first tab controller (or any you want), and hide it at the controllers you don't want on their viewDidLoad like this:
self.navigationController?.isNavigationBarHidden = true
Doing this, you will be able to see the back Button at the controllers you pushed and didn't hide the navigationBar.
Make sure you push the controller using the navigation controller like this:
self.navigationController?.pushViewController(YOUR VIEW CONTROLLER, animated: true)
The below code will allow you to create your own Navigation handling class and have the "push" "pop" animation that UINavigationController has.. You can create a new project, copy paste the below into ViewController.swift and see for yourself..
Now you can give any UIViewController navigation controller abilities..
import UIKit
class NavigationHandler : NSObject, UIViewControllerTransitioningDelegate, UIViewControllerAnimatedTransitioning, UINavigationBarDelegate {
private var isPresenting: Bool = false
private weak var controller: UIViewController?
init(controller: UIViewController) {
super.init()
self.controller = controller
controller.transitioningDelegate = self
let navigationBar = UINavigationBar()
controller.view.addSubview(navigationBar)
NSLayoutConstraint.activate([
navigationBar.leftAnchor.constraint(equalTo: controller.view.leftAnchor),
navigationBar.rightAnchor.constraint(equalTo: controller.view.rightAnchor),
navigationBar.topAnchor.constraint(equalTo: controller.view.safeAreaLayoutGuide.topAnchor)
])
navigationBar.translatesAutoresizingMaskIntoConstraints = false
navigationBar.delegate = self
let item = UINavigationItem(title: controller.title ?? "")
let barButton = UIBarButtonItem(title: "Back", style: .done, target: self, action: #selector(onBackButton(button:)))
item.leftBarButtonItems = [barButton]
navigationBar.setItems([item], animated: true)
}
func position(for bar: UIBarPositioning) -> UIBarPosition {
return .topAttached
}
#objc
private func onBackButton(button: UIBarButtonItem) {
self.controller?.dismiss(animated: true, completion: nil)
}
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
self.isPresenting = true;
return self;
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
self.isPresenting = false;
return self;
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.25;
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let duration = self.transitionDuration(using: transitionContext)
let fromController = transitionContext.viewController(forKey: .from)
let toController = transitionContext.viewController(forKey: .to)
let containerView = transitionContext.containerView
if self.isPresenting {
let frame = fromController!.view.frame
containerView.addSubview(toController!.view)
toController?.view.frame = CGRect(x: frame.origin.x + frame.width, y: frame.origin.y, width: frame.width, height: frame.height)
UIView.animate(withDuration: duration, animations: {
fromController?.view.frame = CGRect(x: frame.origin.x - frame.size.width, y: frame.origin.y, width: frame.size.width, height: frame.size.height)
toController?.view.frame = frame
}, completion: { (completed) in
transitionContext.completeTransition(true)
})
}
else {
let frame = fromController!.view.frame
containerView.insertSubview(toController!.view, at: 0)
toController?.view.frame = CGRect(x: frame.origin.x - frame.size.width, y: frame.origin.y, width: frame.size.width, height: frame.size.height)
UIView.animate(withDuration: duration, animations: {
fromController?.view.frame = CGRect(x: frame.origin.x + frame.width, y: frame.origin.y, width: frame.width, height: frame.height)
toController?.view.frame = frame
}, completion: { (completed) in
transitionContext.completeTransition(true)
})
}
}
}
View Controllers for testing:
class ViewController : UIViewController {
private var navigationHandler: NavigationHandler?
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton(type: .custom)
button.setTitle("Push Controller", for: .normal)
button.setTitleColor(UIColor.red, for: .normal)
button.layer.borderColor = UIColor.black.cgColor
button.layer.borderWidth = 1.0
button.layer.cornerRadius = 5.0
button.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(button)
NSLayoutConstraint.activate([
button.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
button.centerYAnchor.constraint(equalTo: self.view.centerYAnchor),
button.heightAnchor.constraint(equalToConstant: 45.0),
button.widthAnchor.constraint(equalToConstant: 150.0)
])
button.addTarget(self, action: #selector(onPush(button:)), for: .touchUpInside)
}
#objc
private func onPush(button: UIButton) {
let child = ChildViewController()
self.navigationHandler = NavigationHandler(controller: child)
self.present(child, animated: true, completion: nil)
}
}
class ChildViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.blue
}
}
I have a UISearchBar in my application. When I press a button to "open" the searchbar does a new view appear. But the problem is that the NavigationController changes and the UISearchBar disappear. How can I do so I can keep the current NavigationController with my searchbar even if a new view appear. (So I still searching when the new view appear)
P.s my code is not the best and I´m not using Storyboard!
class HomeController: UICollectionViewController, UICollectionViewDelegateFlowLayout, UISearchBarDelegate {
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.backgroundColor = UIColor.white
setupNavigationBar()
}
Here is the new view that appear:
class UserSearchController: UICollectionViewController, UISearchBarDelegate {
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.backgroundColor = UIColor.blue
}
}
And here is the whole searchbar code:
import UIKit
var searchBar = UISearchBar()
var searchBarButtonItem: UIBarButtonItem?
var logoImageView: UIImageView!
extension HomeController {
func setupNavigationBar() {
let button = UIButton(type: .system)
button.setImage(#imageLiteral(resourceName: "search"), for: .normal)
button.addTarget(self, action: #selector(showSearchBar), for: .touchUpInside)
button.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
let barButton = UIBarButtonItem(customView: button)
self.navigationItem.rightBarButtonItem = barButton
let logoImage = UIImage(named: "home")!
logoImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: logoImage.size.width, height: logoImage.size.height))
logoImageView.image = logoImage
navigationItem.titleView = logoImageView
searchBar.delegate = self
searchBar.searchBarStyle = UISearchBarStyle.minimal
searchBar.placeholder = "Search"
searchBar.barTintColor = UIColor.gray
searchBarButtonItem = navigationItem.rightBarButtonItem
}
func showSearchBar() {
let layout = UICollectionViewFlowLayout()
let userSearchController = UserSearchController(collectionViewLayout: layout)
self.navigationController?.pushViewController(userSearchController, animated: true)
searchBar.alpha = 0
navigationItem.titleView = searchBar
navigationItem.setLeftBarButton(nil, animated: true)
navigationItem.rightBarButtonItem = nil
searchBar.showsCancelButton = true
UIView.animate(withDuration: 0.5, animations: {
self.searchBar.alpha = 1
}, completion: { finished in
self.searchBar.becomeFirstResponder()
})
}
public func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
hideSearchBar()
}
func hideSearchBar() {
navigationItem.setRightBarButton(searchBarButtonItem, animated: true)
logoImageView.alpha = 0
UIView.animate(withDuration: 0.3, animations: {
self.navigationItem.titleView = self.logoImageView
self.logoImageView.alpha = 1
}, completion: { finished in
})
}
}
The reason why your search bar is only visible on your first View Controller is because you are using the View Controller's titleView property. Each UIViewController has it's own titleView property, so if you push a View Controller onto your first VC, it will also need to have the titleView property set to a search bar view with the required configuration.
I think you can create a base class, add UISearchBar above the base class, and then you want the current controller with UISearchBar to inherit your base class