By default, Navigation back button text comes as previous screen title or <
I am trying to change that to just <=|
But Its coming as shown in the picture
BackButton Image.
So, I want to know how to change its font to make big <=| and remove the default <
I tried
Tried the same code in viewDidLoad of first start screen,
So i also want to know where to place this code:
override func viewWillAppear(animated: Bool)
{
self.navigationItem.leftBarButtonItem?.title = "<=|"
let FntStgVal = [NSFontAttributeName:UIFont.systemFontOfSize(50, weight: UIFontWeightLight)]
self.navigationItem.leftBarButtonItem?.setTitleTextAttributes(FntStgVal, forState: .Normal)
}
Change your code in viewDidLoad like this.
class BaseViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
func setNavigationWithCustomBackButton() {
let btnLeft:UIButton! = UIButton(frame: CGRectMake(0, 0, 20, 16))
btnLeft.setTitle("<=|", forState: .Normal)
btnLeft.titleLabel?.font = UIFont.systemFontOfSize(19, weight: UIFontWeightLight)
btnLeft!.addTarget(self, action: "handleBack:",forControlEvents: UIControlEvents.TouchUpInside)
let leftItem:UIBarButtonItem = UIBarButtonItem(customView: btnLeft!)
self.navigationItem.leftBarButtonItem = leftItem
}
func handleBack(sender: UIButton) {
self.navigationController?.popViewControllerAnimated(true)
}
}
Now use this BaseViewController as parent of your all viewController and call its method in viewDidLoad like this.
class ViewController1: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.setNavigationWithCustomBackButton()
}
}
Now it will add custom back button in your NavigationBar.
Hope this will help you.
Related
I have an app that has multiple views and one of them is a SceneView as you can see below. The SceneView is not the initial view. Users can open the SceneView by triggering a segue from another ViewController but when I want to add a nav bar to the SceneView, an error has occured.
So how can I add a navigation bar or a button to a sceneView? If there is no way, how can I manage to dismiss segue from the SceneView?
I couldn't find a way to add a navigationBar on top of a sceneView but I figured out how to add a button to a sceneView. If we create a button programmatically and add that button to the sceneView, we can navigate via that button.
Here's what we can do;
import UIKit
import SceneKit
class ViewController: UIViewController {
var scnView: SCNView?
var exampleScn = SCNScene(named: "ExampleScn")
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.scnView = SCNView(frame: self.view.frame)
self.scnView?.scene = exampleScn
self.view.addSubview(self.scnView!)
let button = UIButton(type: .system)
button.tintColor = UIColor.black
button.titleLabel?.font = UIFont(name: "Arial", size: 25)
button.setTitle("Back", for: .normal)
button.sizeToFit()
button.addTarget(self, action: #selector(didPressBack), for: .touchUpInside)
button.center.x = 50
button.frame.origin.y = 20
scnView.addSubview(button)
}
#objc func didPressBack (sender: UIButton!) {
dismiss(animated: true, completion: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
if we do this, the result will look like this:
I have two super view controllers MasterCategoryListViewController and MasterCategoryItemViewController.
I want to use these in several apps.
I inherit from both of these
class CustomListController: MasterCategoryListViewController
class CustomItemController: MasterCategoryItemViewController
Now in the MasterCategoryListViewController
I have a button handler...
#objc open func btnAddTapped(sender: UIBarButtonItem) {
let itemViewController = MasterCategoryItemViewController()
itemViewController.title = "Type"
self.navigationController?.pushViewController(itemViewController, animated: true)
}
I know I can override the method to push to CustomItemController however, I'm just wondering if I can do this in my MasterCategoryListViewController, obviously without it knowing anything about what CustomItemController?
Create a method on the parent called something like detailVCClass(), answering the class that should be instantiated upon button tap. The parent can answer something generic, and the subclasses answer any class that's appropriate for each to know about.
Have the button tap method instantiate an instance of self.detailVCClass(), rather than a class name literal.
however, I'm just wondering if I can do this in my
MasterCategoryListViewController, obviously without it knowing
anything about what CustomItemController ?
Yes. Why not? However, the one that will be pushed is the MasterCategoryItemViewController, not any subclassing classes of it. So like what you've mentioned in your question, you know that btnAddTapped can be overriden, so do it like that.
OR, you could do something a bit more interesting:
In your MasterCategoryListViewController, have an object of MasterCategoryItemViewController. Then in your CustomListController, apply any subclassing MasterCategoryListViewController class. Next, push that MasterCategoryItemViewController object in your btnAddTapped()
Complete sample:
import UIKit
class ListVC: MasterListVC {
override func viewDidLoad() {
super.viewDidLoad()
self.title = "ListVC"
self.itemVCToBePushed = ItemVC2()
}
}
class MasterListVC: UIViewController {
var itemVCToBePushed: MasterItemVC?
lazy var button: UIButton = {
let button = UIButton(type: .custom)
button.frame = CGRect(x: 100, y: 100, width: 250.0, height: 44.0)
button.setTitle("Test", for: .normal)
button.addTarget(self, action: #selector(self.pushMe), for: .touchUpInside)
button.backgroundColor = .gray
return button
}()
#objc func pushMe() {
guard let itemVCToBePushed = self.itemVCToBePushed else { return }
self.navigationController?.pushViewController(itemVCToBePushed, animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
self.title = "MasterListVC"
self.view.backgroundColor = .white
self.view.addSubview(self.button)
}
}
/////
class ItemVC: MasterItemVC {
override func viewDidLoad() {
super.viewDidLoad()
self.title = "ItemVC"
}
}
class ItemVC2: MasterItemVC {
override func viewDidLoad() {
super.viewDidLoad()
self.title = "ItemVC2"
}
}
class MasterItemVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.title = "MasterItemVC"
self.view.backgroundColor = .white
}
}
The code below compiles fine, but crashes with an unrecognized selector sent to instance error.
I have one class that inherits from UIViewController:
class Controller: UIViewController {
override func viewDidLoad() {
let toolbarWrapper = CustomToolbarWrapper(view: view, target: self)
let toolbar = toolbarWrapper.toolbarView
view.addSubview(toolbar)
... Other code ...
}
}
And another class that is just a wrapper for a UIView and contains buttons:
class CustomToolbarWrapper {
var toolbarView: UIView
init(view: UIView, target: Any) {
let height: CGFloat = 80
toolbarView = UIView(frame: CGRect(x: 0, y: view.frame.height - height, width: view.frame.width, height: height))
let button = UIButton()
... Some button layout code ...
button.addTarget(target, action: #selector(CustomToolbar.buttonTapped(_:)), for: .touchUpInside)
toolbarView.addSubview(button)
}
#objc static func buttonTapped(_ sender: Any) {
print("button tapped")
}
}
For the sake of clarity, I left out a large chunk of code and kept what I thought was necessary. I think that my code doesn't work because of my misunderstanding of the how the target works in the addTarget function. Normally, I would just use self as the target of my button's action, so I just tried to pass along self from the view controller to the CustomToolbarWrapper's init function.
What else I have tried:
Changing the button's target from target to self like this:
button.addTarget(self, action: #selector(CustomToolbar.buttonTapped(_:)), for: .touchUpInside)
results in the app not crashing anymore. Instead, however, I believe that line of code fails to do anything (which doesn't throw an error for some reason?) because attempting to print button.allTargets or even button.allTargets.count results in the app crashing at compile time, with an EXC_BREAKPOINT error and no error description in the console or the XCode UI (which just confuses me even more because there are no breakpoints in my code!).
Also, making buttonPressed(_:) non-static does not change any of the previously mentioned observations.
Also, to make sure the button could in fact be interacted with, I added this in the viewDidLoad() of Controller:
for subview in toolbar.subviews? {
if let button = subview as? UIButton {
button.addTarget(self, action: #selector(buttonPressed(_:)), for: .touchUpInside)
}
}
and added a simple testing method to Controller for the button:
#objc func buttonPressed(_ sender: UIButton) {
print("Button Pressed")
}
And running the code did result in "Button Pressed" being printed in the console log, so the button should be able to be interacted with by the user.
Feel free to let me know if you think this is not enough code to figure out the problem, and I will post more details.
Edit
I prefer to keep the implementation of the button's action in the CustomToolbarWrapper class to prevent repeating code in the future, since the action will be the same no matter where an instance of CustomToolbarWrapper is created.
The best option would be to add the target in your controller and then call a method in your toolbarWrapper on button press. But if you really need to keep this design, you should have a strong reference to your toolbarWrapper in your controller class, otherwise your toolbarWrapper is deallocated and nothing gets called. Also, the buttonTapped(_:) method does not need to be static. Thus, in your controller:
class Controller: UIViewController {
var toolbarWrapper: CustomToolbarWrapper?
override func viewDidLoad() {
toolbarWrapper = CustomToolbarWrapper(view: view, target: self)
let toolbar = toolbarWrapper.toolbarView
view.addSubview(toolbar)
... Other code ...
}
}
And in your wrapper:
class CustomToolbarWrapper {
var toolbarView: UIView
init(view: UIView, target: Any) {
let height: CGFloat = 80
toolbarView = UIView(frame: CGRect(x: 0, y: view.frame.height - height,width: view.frame.width, height: height))
let button = UIButton()
... Some button layout code ...
button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
toolbarView.addSubview(button)
}
#objc func buttonTapped(_ sender: Any) {
print("button tapped")
}
}
There is another way I would use which is delegation. The target does not necessarily have to be a controller, it can be the CustomToolbarWrapper itself.
First, declare a protocol
protocol CTDelegate: AnyObject {
func didClickButton()
}
Then in CustomToolbarWrapper add a property, weak var delegate: CTDelegate? and a button action:
#objc func buttonTapped(_ sender: UIButton) {
delegate?.didClickButton()
}
So in your case, it becomes:
button.addTarget(self, action: #selector(CustomToolbarWrapper.buttonTapped(_:)), for: .touchUpInside)
Then when you go to any ViewController, conform to CTDelegate and initialize the CustomToolbarWrapper, you can set its delegate to the controller.
e.g
let toolbarWrapper = CustomToolbarWrapper(view: view, target: self)
toolbarWrapper.delegate = self
and implement your action inside the method you are conforming to in your controller i.e.
func didClickButton()
Your problem is right here:
let toolbarWrapper = CustomToolbarWrapper(view: view, target: self)
You're passing an instance of Controller class which doesn't implement the buttonTapped(_:) selector. It is implemented by your CustomToolbarWrapper class. This is a bad design in general. You should either follow a delegate pattern, or a callback pattern.
Updated Answer:
Delegate pattern solution:
class Controller: UIViewController, CustomToolbarWrapperDelegate {
override func viewDidLoad() {
let toolbarWrapper = CustomToolbarWrapper(view: view, buttonDelegate: self)
let toolbar = toolbarWrapper.toolbarView
view.addSubview(toolbar)
}
// MARK: - CustomToolbarWrapperDelegate
func buttonTapped(inToolbar toolbar: CustomToolbarWrapper) {
print("button tapped")
}
}
protocol CustomToolbarWrapperDelegate: AnyObject {
func buttonTapped(inToolbar toolbar: CustomToolbarWrapper) -> Void
}
class CustomToolbarWrapper {
var toolbarView: UIView
weak var buttonDelegate: CustomToolbarWrapperDelegate?
init(view: UIView, buttonDelegate: CustomToolbarWrapperDelegate?) {
let height: CGFloat = 80
toolbarView = UIView(frame: CGRect(x: 0, y: view.frame.height - height, width: view.frame.width, height: height))
self.buttonDelegate = buttonDelegate
let button = UIButton()
button.addTarget(self, action: #selector(self.buttonTapped(_:)), for: .touchUpInside)
toolbarView.addSubview(button)
}
#objc private func buttonTapped(_ sender: Any) {
// Your button's logic here. Then call the delegate:
self.buttonDelegate?.buttonTapped(inToolbar: self)
}
}
If you'd rather stick to your current design then just implement the following changes:
class Controller: UIViewController {
override func viewDidLoad() {
let toolbarWrapper = CustomToolbarWrapper(view: view, target: self, selector: #selector(self.buttonTapped(_:)), events: .touchUpInside)
let toolbar = toolbarWrapper.toolbarView
view.addSubview(toolbar)
}
#objc private func buttonTapped(_ sender: Any) {
print("button tapped")
}
}
class CustomToolbarWrapper {
var toolbarView: UIView
init(view: UIView, target: Any?, selector: Selector, events: UIControlEvents) {
let height: CGFloat = 80
toolbarView = UIView(frame: CGRect(x: 0, y: view.frame.height - height, width: view.frame.width, height: height))
let button = UIButton()
button.addTarget(target, action: selector, for: events)
toolbarView.addSubview(button)
}
}
I’m trying to change the default look of Navigation Controller back button across my app.
I would like to use and image(icon) and remove the “Back” text.
This code stretched the image across the button and does not remove the button title.
let backImg: UIImage = UIImage(named: "icon_back")!
UIBarButtonItem.appearance().setBackButtonBackgroundImage(backImg, for: .normal, barMetrics: .default)
An recommendation how to do is inside AppDelegate (didFinishLaunchingWithOptions)?
The easy way of doing is just create one BaseViewController for your project which is derived from UIViewController. You can have on common method in BaseViewController to create your custom leftBarButton in every viewController. The remaining viewController in your project should be derived from this BaseViewController,
class BaseViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
createLeftBarButton(image: #Pass image here#, width: #Pass width of your image view#) // Create custom back bar button.
}
/**Create cutom back bar button*/
func createLeftBarButton(image: UIImage?, width: CGFloat) {
let backButton: UIButton = UIButton(frame: CGRect(x: 0, y: 0, width: width, height: 50))
backButton.imageView?.contentMode = .scaleAspectFill
backButton.imageView?.bounds = CGRect(x: 0, y: 0, width: width, height: width)
backButton.setImage(image, for: .normal)
backButton.setImage(image, for: .highlighted)
backButton.addTarget(self, action: #selector(leftBarButtonItemPressed(_:)), for: .touchUpInside)
let leftItem: UIBarButtonItem = UIBarButtonItem(customView: backButton)
navigationItem.leftBarButtonItem = leftItem
}
/**Custom back bar button pressed. So handle here*/
func leftBarButtonItemPressed(_ sender: UIButton) {
view.endEditing(true) // End editing if any.
if isViewControllerPresented() { // Check view controller is presented or pushed
dismiss(animated: true, completion: nil) // Dismiss ViewController if presented
} else {
_ = navigationController?.popViewController(animated: true) // Pop ViewController if pushed
}
}
/**To check whether view controller is presented or pushed.*/
func isViewControllerPresented() -> Bool {
if self.presentingViewController?.presentedViewController == self {
return true
}
if (self.navigationController != nil && self.navigationController?.presentingViewController?.presentedViewController == self.navigationController) && self.navigationController?.viewControllers.count == 1 {
return true
}
if self.tabBarController?.presentingViewController is UITabBarController {
return true
}
return false
}
}
// Sub class your remaining viewControllers like this.
class FirstViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad() // When calling this super method, the custom back bar button will be created for you
}
}
Thanks.
You can doing this by setting the navigationItem's BackButtonBackgroundImage;
also you can set the appearance for global effect:
let item = UIBarButtonItem.appearance(whenContainedInInstancesOf: [UINavigationBar.self])
item.setBackButtonBackgroundVerticalPositionAdjustment(-10, for: .default) item.setBackButtonBackgroundImage(youImage, for: .normal, barMetrics: .default)
for removing the back title, you can do like this:
#implementation UINavigationItem (BackItem)
-(UIBarButtonItem *)backBarButtonItem {
UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithTitle:#" " style:UIBarButtonItemStylePlain target:nil action:nil];
item.tintColor = [UIColor darkGrayColor];
return item;
}
#end
I have a custom class Overlay where I added UIButton. When the button is clicked, a method should be called:
class Overlay {
func show(onView view: UIView, frame: CGRect) {
let dismissButton = UIButton()
dismissButton.frame = frame
dismissButton.setTitle("Dismiss", for: .normal)
dismissButton.setTitleColor(Project.Color.failure, for: .normal)
dismissButton.titleLabel?.font = Project.Typography.lightFont.withSize(22)
view.addSubview(dismissButton)
dismissButton.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(dismissBtnTapped(tap:)))
dismissButton.addGestureRecognizer(tap)
}
#objc func dismissBtnTapped(tap: UITapGestureRecognizer) {
print("TEST")
}
I call show(...) inside my ViewController, passing in its view and a frame.
But the tapGestrueRecognizer is not working. Any ideas?
Thank you.
Edit: I tried putting this code directly inside my ViewController. Then it works. I'm not sure why, though, and that's not a viable solution for me, unfortunately :/
Edit 2:
That's how I call it:
let overlay = Overlay()
overlay.show(onView: self.view, frame: CGRect(x: 0, y: 0, width: view.bounds.width, height: 150))
You are already adding a button try adding a target to it instead of a gesture,
and make your overlay variable global.
class YourControllerClass: UIViewController {
let overlay = Overlay()
...
func show(onView: UIView, frame: CGRect) {
...
dismissButton.addTarget(self, action: #selector(dismissBtnTapped(_:)), forControlEvents: .TouchUpInside)
}
func dismissBtnTapped(sender:UIButton){
}
}
Hope this helps.
You not need to add tapgesturerecognizer on uibutton, you can directly add target on it something like,
dismissButton.addTarget(self, action: #selector(dismissBtnTapped), forControlEvents: .TouchUpInside)
and remove parameter from dismissBtnTapped method!
Make sure your view which you are passing as onView in show(onView: UIView, frame: CGRect) method is enabled for user interactions.
As an example my script
step 1
class MyIcon {
var targetController = UIViewController()
func show(_ targetController:UIViewController, _ view: UIView, _ frame: CGRect) {
self.targetController = targetController
let icon = UIImageView()
icon.frame = frame
icon.image = UIImage(named: "image_user.png")
icon.isUserInteractionEnabled = true
view.addSubview(icon)
let tap = UITapGestureRecognizer(target: self.targetController, action: #selector(iconTapped))
icon.addGestureRecognizer(tap)
}
#objc func iconTapped(_ icon: UIImageView) {
print("Yo hoo! This worked!")
}
}
step 2
class MyController: UIViewController{
let myicon = MyIcon()
override func viewDidLoad() {
super.viewDidLoad()
myicon.show(self, self.view, self.view.frame)
}
#IBAction func iconTapped(_ icon: UIImageView){
myicon.iconTapped(icon)
}
}
Non of the above solve the problem here.
The point is, if you create the instance of the custom class inside of an event the instance is deleted after the event is completed since it is not associated with any persistent element in your app. Thats why you have to instantiate the object of the class as an attribute of the superview.