-1

I am making an app, and there is an instance where I do not want to use a navigation controller, and I just want to use a navigation bar. I want a back button, but I cannot get it so that there is the wide chevron.

As in it looks like this:

enter image description here

instead of this:

enter image description here

How do I achieve the second effect without a navigation controller?

Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116
D-A UK
  • 1,084
  • 2
  • 11
  • 24
  • 4
    why can't you use a NavigationController? This feels quite hacky. What is happing if you tap on "Back"? there should be pop animation – cornr Jan 07 '18 at 20:00
  • 1
    See https://stackoverflow.com/questions/26518716/unicode-characters-that-look-like-ios7-left-pointing-navigation-chevron – rmaddy Jan 07 '18 at 20:03
  • Yep it's as @rmaddy says or you create your own image for it. Of course without a UINavigationController you also need to provide all the back functionality yourself as well. – Upholder Of Truth Jan 07 '18 at 20:17
  • Would be nice if you gave some code too. –  Jan 07 '18 at 20:19
  • Why do I need to give code? – D-A UK Jan 07 '18 at 20:29
  • What @dfd meant by "giving" was _sharing_ some code. It's necessary to see what you're dealing with in order to get some context and help you. – Mauricio Chirino Jan 07 '18 at 20:50
  • Thanks @MauricioChirino. That's exactly what I meant. Maybe I'm having a rough day - or just a rough day communicating - but yeah. There's context, and then there's **context**. The question shows no real use of anything tried - just asked for something without anything else. I didn't mean to imply (or offend) but what was *given* was actually very little. To the OP: could you please *share* what you've tried? –  Jan 07 '18 at 21:34

1 Answers1

3

One option is to render your own chevron to a UIImage and set that as the bar button's image..

//
//  ViewController.swift
//  StackOverflow
//
//  Created by Brandon on 2018-01-07.
//  Copyright © 2018 XIO. All rights reserved.
//

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()


        //Creating a custom navigation bar..
        let navigationBar = UINavigationBar()
        self.view.addSubview(navigationBar)
        NSLayoutConstraint.activate([
            navigationBar.leftAnchor.constraint(equalTo: self.view.leftAnchor),
            navigationBar.rightAnchor.constraint(equalTo: self.view.rightAnchor),
            navigationBar.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor)
        ])
        navigationBar.translatesAutoresizingMaskIntoConstraints = false

        //Creating a navigation bar item with title..
        let item = UINavigationItem(title: "Custom Navigation")

        //Creating the chevron (back-arrow)
        //let img = makeBackChevron(size: CGSize(width: 20.0, height: 20.0), colour: nil)! //UIColor.red

        //Creating the chevron (back-arrow) to look like Apple's..
        let img = makeBackChevron(thickness: 3.0, size: CGSize(width: 22.0, height: 44.0), colour: nil)! //UIColor.red

        //Creating the bar button.. Note: Add your own target and action..
        let barButton = UIBarButtonItem(image: img, style: .done, target: nil, action: nil)

        //Set the left bar button item to be the one we created
        //Then set the items to be part of the navigation bar we created..
        item.leftBarButtonItems = [barButton]
        navigationBar.setItems([item], animated: true)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    //Creates a chevron (back-arrow) image with size and colour..
    func makeBackChevron(size: CGSize, colour: UIColor? = nil) -> UIImage? {
        //Create a rendering context..
        UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale)
        let ctx = UIGraphicsGetCurrentContext()

        //Create a chevron path with normalized 2D coordinates..
        let path = UIBezierPath()
        path.move(to: CGPoint(x: 1.0, y: 0.0)) //top right..
        path.addLine(to: CGPoint(x: 0.75, y: 0.0)) //top left..
        path.addLine(to: CGPoint(x: 0.0, y: 0.5))  //left center of pointy arrow head..
        path.addLine(to: CGPoint(x: 0.75, y: 1.0)) //bottom left..
        path.addLine(to: CGPoint(x: 1.0, y: 1.0)) //bottom right..
        path.addLine(to: CGPoint(x: 0.25, y: 0.5)) //right center of pointy arrow head..
        path.close()

        //Scale the path to be the size specified..
        path.apply(CGAffineTransform(scaleX: size.width, y: size.height))

        //Set rendering colour..
        if let colour = colour {
            ctx?.setFillColor(colour.cgColor)
        }

        //Draw the path to the image context..
        ctx?.addPath(path.cgPath)
        ctx?.fillPath()

        //Create the image from the context..
        let img = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        //If a colour was set, then always render the image with that colour.. else allow navigation bar or any view to `tint` the image..
        return colour != nil ? img?.withRenderingMode(.alwaysOriginal) : img
    }

    //Closer to the Apple chevron.. Allows you to specify arrow-thickness..
    func makeBackChevron(thickness: CGFloat, size: CGSize, colour: UIColor? = nil) -> UIImage? {
        UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale)
        let ctx = UIGraphicsGetCurrentContext()

        //Create a thin-line chevron with some left-padding..
        let padding: CGFloat = 0.20
        let path = UIBezierPath()
        path.move(to: CGPoint(x: padding + 0.5, y: 0.773))
        path.addLine(to: CGPoint(x: padding + 0.0, y: 0.5))
        path.addLine(to: CGPoint(x: padding + 0.5, y: 0.227))
        path.apply(CGAffineTransform(scaleX: size.width, y: size.height))

        //Use a stroke instead of a fill like previous algorithm..
        ctx?.setStrokeColor(colour?.cgColor ?? UIColor.white.cgColor)
        ctx?.addPath(path.cgPath)
        ctx?.setLineWidth(thickness) //Set arrow-thickness..
        ctx?.setLineJoin(.round) //Set line-join to round corners..
        ctx?.strokePath()

        let img = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return colour != nil ? img?.withRenderingMode(.alwaysOriginal) : img
    }
}

Note: I left the drawing of text as an exercise to the reader ;)

Results:

enter image description here

Brandon
  • 22,723
  • 11
  • 93
  • 186
  • Edit: Added a new algorithm for creating the chevron so it look's closer to the one in OP's post.. (very similar to Apple's).. If you want to change chevron direction, you can apply another transform with rotation or negated the scale. It's not "perfect" and doesn't match "exactly" like Apple's but it is close enough! I can't spend more time on numbers and calculations.. – Brandon Jan 07 '18 at 21:55
  • 2
    for those who are looking for back button with title (based on this solution): https://imgur.com/a/FPUKrVU `let img = makeBackChevron(thickness: 3.0, size: CGSize(width: 22.0, height: 44.0), colour: nil)! let backButton = UIButton(type: .custom) backButton.setImage(img, for: .normal) backButton.setTitle("Back", for: .normal) backButton.sizeToFit() backButton.addTarget(self, action: #selector(self.backButtonClick), for: .touchUpInside) self.navigationItem.leftBarButtonItems = [UIBarButtonItem(customView: backButton)]` – coldembrace Jan 01 '19 at 19:55