Changing the back button of UINavigaitonBar with MVVM+C - swift

I am using MVVM+C pattern to build my app. Currently I am facing a problem with changing the native back button title and image of navigation bar to the custom image without the title. I've tried a lots of solutions what I was able to find, but nothing set the different title or even an image. I've ended up with this code in AppDelegate.swift:
let navigationController: UINavigationController = .init()
if #available(iOS 13.0, *) {
let appearence = UINavigationBarAppearance()
appearence.configureWithOpaqueBackground()
appearence.backgroundColor = .backgroundColor
appearence.shadowColor = nil
appearence.shadowImage = nil
navigationController.navigationBar.standardAppearance = appearence
navigationController.navigationBar.scrollEdgeAppearance = navigationController.navigationBar.standardAppearance
} else {
navigationController.navigationBar.isTranslucent = false
navigationController.navigationBar.barTintColor = .backgroundColor
navigationController.navigationBar.shadowImage = nil
navigationController.navigationBar.shadowColor = nil
}
// This code is not working at all, always get "Back" as a default with default image =====
let backButtonBackgroundImage = UIImage(named: "backButton")
navigationController.navigationBar.backIndicatorImage = backButtonBackgroundImage
navigationController.navigationBar.backIndicatorTransitionMaskImage = backButtonBackgroundImage
let backBarButtton = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
navigationController.navigationItem.backBarButtonItem = backBarButtton
// =========
navigationController.navigationBar.tintColor = .primary
window?.rootViewController = navigationController
window?.makeKeyAndVisible()
Also, I've followed the official documentation but without any success. As default I've set the navigation bar as hidden (because is not needed for multiple times) and I am showing it in ViewWillAppear and hiding in ViewWillDisappear methods.
Is there someone who has an idea of what's going on? Thanks!
The result of this code:
Expected result:
This is what I get with the new code:
SOLUTION:
After using code from Scott I was able to change the image and look of the navigation bar but I lost the ability to swipe back. After adding this code to the UINavigationBar extension I was able to get it back:
extension UINavigationController: UIGestureRecognizerDelegate {
#objc func goBack(sender: Any?) {
self.popViewController(animated: true)
}
override open func viewDidLoad() {
super.viewDidLoad()
interactivePopGestureRecognizer?.delegate = self
}
public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return viewControllers.count > 1
}
}

Below is some Playground code that shows a UINavigationController with a custom back button that is an image.
Note that what it does is hides the system provided back button, then substitutes another button that still performs the "back" action but on a custom UINavigationController.
There may be a more efficient way to duplicate the functionality of "back" that doesn't involve a custom class and a custom target-action setup, but I couldn't find one quickly so finding that solution can be left as an exercise for the reader.
import UIKit
import SwiftUI
import PlaygroundSupport
NSSetUncaughtExceptionHandler { error in
debugPrint(error)
}
class MyNavController : UINavigationController {
#objc func goBack(sender: Any?) {
self.popViewController(animated: true)
}
}
let navDestination1 = UIViewController()
navDestination1.navigationItem.title = "Destination 1"
let navigationController = MyNavController(rootViewController: navDestination1)
if #available(iOS 13.0, *) {
let appearence = UINavigationBarAppearance()
appearence.configureWithOpaqueBackground()
appearence.backgroundColor = .purple
appearence.shadowColor = nil
appearence.shadowImage = nil
navigationController.navigationBar.standardAppearance = appearence
navigationController.navigationBar.scrollEdgeAppearance = navigationController.navigationBar.standardAppearance
} else {
navigationController.navigationBar.isTranslucent = false
navigationController.navigationBar.barTintColor = .purple
navigationController.navigationBar.shadowImage = nil
}
let navDestination2 = UITableViewController()
navDestination2.navigationItem.title = "Destination 2"
navDestination2.navigationItem.hidesBackButton = true
navDestination2.navigationItem.leftBarButtonItem = UIBarButtonItem(
image: UIImage(systemName: "multiply.circle.fill"),
style: UIBarButtonItem.Style.done,
target: navigationController,
action: #selector(MyNavController.goBack))
navigationController.pushViewController(navDestination2, animated: true)
navigationController.view.bounds = CGRect(x: 0,y: 0,width: 320,height: 480)
PlaygroundSupport.PlaygroundPage.current.liveView = navigationController

Related

Custom shutter button in cameraOverlayView not executing

I am making a custom camera in Swift. I declared it global like this:
let image = UIImagePickerController()
I have made OverlayVC (UIViewController) in IB. Made a shutter button and hooked it up like this:
#IBAction func shutterTapped(_ sender: Any) {
print("shutterTapped")
image.takePicture()
}
I instantiate this overlay before presenting:
image.delegate = self
image.sourceType = .camera
image.cameraDevice = .front
image.allowsEditing = false
let overlay = self.storyboard?.instantiateViewController(withIdentifier: "OverlayVC")
image.cameraOverlayView = overlay?.view
image.showsCameraControls = false
self.present(image, animated: true, completion: nil)
Now when I run the build on device and tap on the shutter button, I can visually see it being tapped (fade out/in) but the code in shutterTapped() never executes.
Why is it not really working?
Adding view of the view controller removes controller itself and no one can actually get the event. So, you get the view but not events. and to support this:
Solution that will actually work with your code would be:
image.cameraOverlayView = overlay?.view
if let viewController = overlay {
for view in viewController.view.subviews {
if let button = view as? UIButton {
button.addTarget(self, action: #selector(self.takePicture), for: UIControlEvents.touchDown)
}
}
}
You can also add tag to it so you can check which button is which if you have more of them.
Alternate Solution:
Create UIView subclass and xib for it. Same view you had in the OverlayVC. Then, when adding overlay use following:
image.sourceType = .camera
image.cameraDevice = .front
image.allowsEditing = false
let myOverlay = Bundle.main.loadNibNamed("CameraOverlay", owner: self, options: nil)
image.cameraOverlayView = myOverlay?.first as! UIView
image.showsCameraControls = false
self.present(image, animated: true, completion: nil)
Note that force unwrap should be protected with either guard or if let when casting to UIView.

Navigation between two UIViewControllers in Swift 3 programmatically

In my app have two UIViewControllers (first, where user choose character, and second, where characters info is shown).
Trying do everything from code. So my code:
AppDelegate.swift:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
self.window = UIWindow(frame: UIScreen.main.bounds)
guard let window = self.window else {
return false
}
UIApplication.shared.statusBarStyle = .lightContent
let viewController = ViewController()
let navigationController = UINavigationController(rootViewController: viewController)
window.rootViewController = navigationController
window.makeKeyAndVisible()
return true
}
ViewController: UIViewController
...
func buttonPressed(sender: UIButton) {
guard let typeName = sender.title(for: .normal), let unitType = UnitType(rawValue: typeName) else {
return
}
let unitViewController = UnitViewController(unitType: unitType)
let navigationController = UINavigationController(rootViewController: unitViewController)
self.present(navigationController, animated: true, completion: nil)
}
And UnitViewController: UIViewController (where chosen character's info is shown):
...
fileprivate func setupNavigationBar() {
let backButton = UIBarButtonItem(title: "<", style: .plain, target: self, action: #selector(backToViewController(sender:)))
self.navigationItem.setLeftBarButton(backButton, animated: true)
self.navigationItem.titleView?.backgroundColor = AppColor.Black.color
self.navigationItem.titleView?.tintColor = .white
self.navigationItem.title = "\(self.unitType.rawValue)"
}
func backToViewController(sender: AnyObject) {
self.dismiss(animated: true) {
let viewController = ViewController()
let navigationController = UINavigationController(rootViewController: viewController)
self.present(navigationController, animated: true, completion: nil)
}
}
So I have two screens:
Well, and I have some questions:
1. Getting
2016-12-02 05:48:21.855 The API Awakens[16402:551729] Warning: Attempt
to present on
whose view is not
in the window hierarchy!
warning when press 'back' button in UnitViewController. What I am doing wrong?
Gray background color and black color of NavigationBar. How change it to black and white?
How get 'system' back button for UIBarButtonItem, not just .plain with title "<"? So my UnitViewController navigation bar should looks like this:
Any help will be greatly appreciated!
UPDATE 1:
1. Warning gone, thanks to #dip
2. Made navigation bar dark, thanks to #aznelite89.
But, there is my code in AppDelegate.swift:
UINavigationBar.appearance().barTintColor = AppColor.Black.color
AppColor.Black is exact same color I'm using for background in ViewController, but that how it looks now:
Looks like alpha of NavigationBar is not 1.0...
UPDATE 2:
Different between color of NavigationBar and color I've used is 13 in RGB values, so I've hacked it setting color of NavigationBar with RGB value less by 13 than original color... It's ok now
Try this :-
let viewController = ViewController()
viewController.modalPresentationStyle = .overCurrentContext
window?.rootViewController?.present(viewController, animated: true, completion: nil)
Add this in app delegate.swift :-
UINavigationBar.appearance().barTintColor = UIColor.black
You may change these attributes on your storyboard attribute inspector

Change the style of the UISearchController views?

Question:
How do I change the color/theme/style of the UISearchController in tvOS?
( Would appear I need to set UIStatusBarStyle some way, since the preferredStatusBarStyle override does not exist in tvOS for UISearchController)
Description:
Method called in my AppDelegate to programmatically create a searchNavigationController with on top a UISearchController and on bottom my custom UITableViewController called "searchResultsController" (SearchViewController)
func configueSearchController() -> UIViewController {
let storyboard = UIStoryboard(name: "Search", bundle: nil)
guard let searchResultsController = storyboard.instantiateViewController(withIdentifier: SearchViewController.storyboardIdentifier) as? SearchViewController else {
fatalError("Unable to instatiate a SearchResultViewController from the storyboard.")
}
/*
Create a UISearchController, passing the `searchResultsController` to
use to display search results.
*/
let searchController = UISearchController(searchResultsController: searchResultsController)
searchController.searchResultsUpdater = searchResultsController
searchController.searchBar.placeholder = NSLocalizedString("Enter keyword (e.g. Gastric Bypass)", comment: "")
// Contain the `UISearchController` in a `UISearchContainerViewController`.
let searchContainer = UISearchContainerViewController(searchController: searchController)
searchContainer.title = NSLocalizedString("Search", comment: "")
// Finally contain the `UISearchContainerViewController` in a `UINavigationController`.
let searchNavigationController = UINavigationController(rootViewController: searchContainer)
return searchNavigationController
}
Image of the visual representation of the above method:
What I've tried:
I've attempted to change the style via different approaches and none of them gave the desired outcome.
This has no effect on the searchBar:
searchController.searchBar.searchBarStyle = .minimal //or .prominent or .default
This only makes the searchBar (actual input area black.. with black text..):
searchController.searchBar.backgroundColor = .black
This only makes the whole background view of the UISearchController black. Everything is black and thus the keyboard can not be seen.
searchController.view.backgroundColor = .black
The ideal solution would be a theme change from the standard .default to something .dark. Because not only does the background color have to change but the borders and text colors must too.
Attempted to implement this solution:
//UISearchController
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
However, this override does not seem to exist for tvOS. Any reason why?
For all those wanting to achieve this on tvOS with a TabBarController and storyboard, what I did was add a container view to my view controller and added the tab bar controller as a child.
class SearchController: UIViewController {
#IBOutlet var containerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let searchController = UISearchController.init(searchResultsController: nil)
searchController.view.autoresizingMask = [.flexibleHeight, .flexibleWidth]
searchController.view.frame = containerView.bounds
let searchContainer: UISearchContainerViewController = UISearchContainerViewController(searchController: searchController)
// Finally contain the `UISearchContainerViewController` in a `UINavigationController`.
let searchNavigationController = UINavigationController(rootViewController: searchContainer)
searchNavigationController.navigationBar.isTranslucent = true
searchNavigationController.navigationBar.tintColor = .black
searchNavigationController.tabBarItem.title = "Search"
containerView.addSubview(searchNavigationController.view)
searchNavigationController.didMove(toParent: self)
}
}
There is a work around to this problem, however I don't really understand why they don't keep the same system as for iOS. Anyway, the main things to do to change the SearchBar style to black is to set the keyboardAppearance to dark, the searchController's view's background color and to set the searchBar's background color to white.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
guard let win = window else {
return true
}
if let tabController = win.rootViewController as? UITabBarController {
tabController.viewControllers?.append(configueSearchController())
}
win.backgroundColor = UIColor.white
win.makeKeyAndVisible()
return true
}
func configueSearchController() -> UIViewController {
let storyboard = UIStoryboard(name: "Search", bundle: nil)
guard let searchResultsController = storyboard.instantiateViewController(withIdentifier: SearchViewController.storyboardIdentifier) as? SearchViewController else {
fatalError("Unable to instatiate a SearchResultViewController from the storyboard.")
}
/*
Create a UISearchController, passing the `searchResultsController` to
use to display search results.
*/
let searchController = UISearchController(searchResultsController: searchResultsController)
searchController.searchResultsUpdater = searchResultsController
searchController.searchBar.placeholder = NSLocalizedString("Enter keyword (e.g. Gastric Bypass)", comment: "")
searchController.view.backgroundColor = UIColor.black
searchController.searchBar.keyboardAppearance = UIKeyboardAppearance.dark
searchController.searchBar.tintColor = UIColor.black
searchController.searchBar.backgroundColor = UIColor.white
searchController.hidesNavigationBarDuringPresentation = false
searchController.obscuresBackgroundDuringPresentation = true
searchController.searchBar.searchBarStyle = .minimal
searchController.searchBar.sizeToFit()
//searchResultsController.tableView.tableHeaderView = searchController.searchBar
// Contain the `UISearchController` in a `UISearchContainerViewController`.
let searchContainer: UISearchContainerViewController = UISearchContainerViewController(searchController: searchController)
// Finally contain the `UISearchContainerViewController` in a `UINavigationController`.
let searchNavigationController = UINavigationController(rootViewController: searchContainer)
searchNavigationController.navigationBar.isTranslucent = true
searchNavigationController.navigationBar.tintColor = .black
searchNavigationController.tabBarItem.title = "Search"
return searchNavigationController
}
I am not an expert on tvOS, but here's what I've found.
I started with the UIKit Catalog: Creating and Customizing UIKit Controls example code from Apple. The first thing that I noticed is that the code you supplied above instantiates the searchResultsController as a SearchViewController, whereas Apple's example code instantiates the searchResultsController as a SearchResultsViewController. I assume you meant to do this.
After playing around with the example code, I was able to make very limited style changes. Here is a screenshot of the closest I have come to what it seems you are trying to achieve.
And here is the code that affected that change.
func packagedSearchController() -> UIViewController {
// Load a `SearchResultsViewController` from its storyboard.
let storyboard = UIStoryboard(name: "ViewControllerSamples", bundle: nil)
guard let searchResultsController = storyboard.instantiateViewController(withIdentifier: SearchResultsViewController.storyboardIdentifier) as? SearchResultsViewController else {
fatalError("Unable to instatiate a SearchResultsViewController from the storyboard.")
}
/*
Create a UISearchController, passing the `searchResultsController` to
use to display search results.
*/
let searchController = UISearchController(searchResultsController: searchResultsController)
searchController.searchResultsUpdater = searchResultsController
searchController.searchBar.placeholder = NSLocalizedString("Enter keyword (e.g. iceland)", comment: "")
//** This is the part you are interested in **//
searchResultsController.view.backgroundColor = .black
searchController.view.backgroundColor = UIColor(red: 0.25, green: 0.25, blue: 0.25, alpha: 1.0)
searchController.searchBar.backgroundColor = .white
// Contain the `UISearchController` in a `UISearchContainerViewController`.
let searchContainer = UISearchContainerViewController(searchController: searchController)
searchContainer.title = NSLocalizedString("Search", comment: "")
// Finally contain the `UISearchContainerViewController` in a `UINavigationController`.
let searchNavigationController = UINavigationController(rootViewController: searchContainer)
return searchNavigationController
}
Unfortunately, I was unable to set the SearchController's view to .black without loosing the keyboard in the darkness. I looked and looked for a solution to the disappearing keyboard problem thinking that there must be a way to style the keyboard, or at least modify the selection state, all to no avail. My disappointment culminated in this statement from the App Programming Guide for tvOS: Designing the Keyboard Experience;
An inline keyboard displays all of the possible inputs on a single line. Use UISearchController to create a keyboard that can be fully integrated with third-party content. However, there are very few customization options when you use UISearchController. You can not access the text field itself, customize the traits, or add input accessories.
I suspect that if you could get a reference to that keyboard, you may be able to affect some change. That being said, I have not been able to find a real solution to date. Maybe you could attach a custom inputAccessoryViewController to the SearchController and effect a change that way?
Sorry I couldn't be more helpful.

How to load popover in swift

I am trying to get a popover working in swift.
The view i am trying to put into the popover is in its own separate xib.
The code to load is below
let view = OrganisationDetails()
view.modalPresentationStyle = .Popover
let popoverMenuViewController = view.popoverPresentationController
view.preferredContentSize = CGSizeMake(550,550)
popoverMenuViewController?.permittedArrowDirections = .Any
popoverMenuViewController?.delegate = self
popoverMenuViewController?.sourceView = sender
popoverMenuViewController?.sourceRect = CGRect(x: 1, y: 1, width: 60, height: 60)
presentViewController(view, animated: true, completion: nil)
What is happening atm is the popover is loading totally blank and not displaying the view.
Any ideas what I'm doing wrong
Thanks
Actually it is much simpler than that. In the storyboard you should make the viewcontroller you want to use as popover and make a viewcontroller class for it as usual. Make a segue as shown below from the object you want to open the popover, in this case the UIBarButton named "Config".
In the "mother viewcontroller" implement the "UIPopoverPresentationControllerDelegate" and the delegate method:
func popoverPresentationControllerDidDismissPopover(popoverPresentationController: UIPopoverPresentationController) {
//do som stuff from the popover
}
Override the "prepareForSeque" method like this:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
//segue for the popover configuration window
if segue.identifier == "yourSegueIdentifierForPopOver" {
if let controller = segue.destinationViewController as? UIViewController {
controller.popoverPresentationController!.delegate = self
controller.preferredContentSize = CGSize(width: 320, height: 186)
}
}
}
And your done. And you can now treat the popover view as any other view, ie. add fields and what not!
This is how I do it
//MARK:idPopupFileTable
let popoverVC = storyboard?.instantiateViewControllerWithIdentifier("idPopupFileTable") as UIViewController
popoverVC.modalPresentationStyle = .Popover
popoverVC.preferredContentSize = CGSizeMake(300, 200)
if let popoverController = popoverVC.popoverPresentationController {
popoverController.sourceView = sender
popoverController.sourceRect = sender.bounds
popoverController.permittedArrowDirections = .Any
popoverController.delegate = self
}
presentViewController(popoverVC, animated: true, completion: nil)
}

UIPopoverController, Xcode 6, IOS 8 using Swift

I'm having some trouble getting a UIPopover to appear using swift. The code that is commented out works fine in Objective-C, but doesn't work using Swift. When I tap the + in my view controller I do get the "click" in my debugger, however no popover appears.
class PlayerInformationTableViewController: UITableViewController, NSFetchedResultsControllerDelegate, UIPopoverControllerDelegate {
#IBOutlet weak var addBarButtonItem: UIBarButtonItem!
var playerInformationViewController = PlayerInformationViewController()
var popover:UIPopoverController? = nil
override func viewDidLoad() {
super.viewDidLoad()
/*
//setup the popover
_cuesPopoverViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"CuesPopoverViewController"];
self.cuesPopover = [[UIPopoverController alloc] initWithContentViewController:_cuesPopoverViewController];
self.cuesPopover.popoverContentSize = CGSizeMake(540, 300);
self.cuesPopover.delegate = self;
*/
playerInformationViewController.storyboard?.instantiateViewControllerWithIdentifier("PlayerInformationViewController")
popover?.contentViewController = playerInformationViewController
popover?.popoverContentSize = CGSizeMake(300, 300)
popover?.delegate = self
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
}
#IBAction func addPopover(sender: AnyObject) {
println("Click")
popover?.presentPopoverFromBarButtonItem(addBarButtonItem, permittedArrowDirections: UIPopoverArrowDirection.Any, animated: true)
}
Solution
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func addPopover(sender: AnyObject) {
var popoverViewController = self.storyboard?.instantiateViewControllerWithIdentifier("PlayerInformationViewController") as UIViewController
popoverViewController.modalPresentationStyle = .Popover
popoverViewController.preferredContentSize = CGSizeMake(450, 450)
let popoverPresentationViewController = popoverViewController.popoverPresentationController
popoverPresentationViewController?.permittedArrowDirections = .Any
popoverPresentationViewController?.delegate = self
popoverPresentationViewController?.barButtonItem = sender as UIBarButtonItem
presentViewController(popoverViewController, animated: true, completion: nil)
}
Here is a simple example for iOS 8. Popover are presented using adaptivity apis in iOS 8.
class PlayerInformationTableViewController: UITableViewController, UIPopoverPresentationControllerDelegate, NSFetchedResultsControllerDelegate{
...
#IBAction func addPopover(sender: UIBarButtonItem){
let playerInformationViewController = PlayerInformationViewController()
playerInformationViewController.modalPresentationStyle = .Popover
playerInformationViewController.preferredContentSize = CGSizeMake(300, 300)
let popoverPresentationViewController = playerInformationViewController.popoverPresentationController
popoverPresentationViewController?.permittedArrowDirections = .Any
popoverPresentationViewController?.delegate = self
popoverPresentationController?.barButtonItem = sender
presentViewController(playerInformationViewController, animated: true, completion: nil)
}
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController!) -> UIModalPresentationStyle{
return .None
}
}
Display Popover with contentView from xib
func showPopover(sender: AnyObject) {
let contentViewController = UINib(nibName: "ContentVC", bundle: nil).instantiateWithOwner(nil, options: nil)[0] as ContentVC
contentViewController.modalPresentationStyle = UIModalPresentationStyle.Popover
var detailPopover: UIPopoverPresentationController = contentViewController.popoverPresentationController!
detailPopover.delegate = self
detailPopover.barButtonItem = sender as UIBarButtonItem
detailPopover.permittedArrowDirections = UIPopoverArrowDirection.Any
presentViewController(contentViewController,
animated: true, completion:nil)
}
Next allows to make not full screen PopoverView on iPhone
for this do not forget to inherit MainViewController: UIPopoverPresentationControllerDelegate and set delegate to PopoverView
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController!) -> UIModalPresentationStyle
{
return .None
}
It looks like your popover is nil. Where are you assigning/instantiating it?
Try changing this:
popover?.presentPopoverFromBarButtonItem(addBarButtonItem, permittedArrowDirections: UIPopoverArrowDirection.Any, animated: true)
To this:
if let pop = popover {
pop.presentPopoverFromBarButtonItem(addBarButtonItem, permittedArrowDirections: UIPopoverArrowDirection.Any, animated: true)
} else {
NSLog("Error: Popover was nil")
}
I imagine you'll see that error message in your console. In the .XIB for your PlayerInformationTableViewController, do you have a UIPopoverController?
If so, you probably need to ensure that the var popover is either (1) being manually instantiated in your awakeFromNib, or that it's an #IBOutlet and is being connected properly.
Alternatively, can you simply use the popover already present in your playerInformationViewController?