2

The effect that I want to achieve is:

enter image description here

And the current state of my app is:

enter image description here

This is the set up of my view controller. I put a tool bar underneath the navigation bar. Then, I set the tool bar's delegate to the navigation bar. I've read several posts about this. One solution that was provided was:

navigationController?.navigationBar.shadowImage = UIImage();
navigationController?.navigationBar.setBackgroundImage(UIImage(), forBarMetrics: .Default)

However, this causes the navigation bar to become white and loses the effect. So I got the following code from this post (UISegmentedControl below UINavigationbar in iOS 7):

    @IBOutlet weak var toolbar: UIToolbar!
    var hairLine: UIView = UIView()
    override func viewDidLoad() {
        super.viewDidLoad()
        doneButton.enabled = false

        for parent in self.navigationController!.navigationBar.subviews {
            for childView in parent.subviews {
                if childView is UIImageView && childView.bounds.size.width == self.navigationController!.navigationBar.frame.size.width {
                    hairLine = childView
                    print(hairLine.frame)
                }
            }
        }
    }

    func removeHairLine(appearing: Bool) {
        var hairLineFrame = hairLine.frame
        if appearing {
            hairLineFrame.origin.y += toolbar.bounds.size.height
        } else {
            hairLineFrame.origin.y -= toolbar.bounds.size.height
        }
        hairLine.frame = hairLineFrame
        print(hairLine.frame)
    }

    override func viewWillAppear(animated: Bool) {
        removeHairLine(true)
    }

    override func viewWillDisappear(animated: Bool) {
        removeHairLine(true)
    }

However, this code removes the hairline before the view is completely loaded but when the view is loaded, it appears again. Any solutions?

Community
  • 1
  • 1
Jae
  • 321
  • 3
  • 12

7 Answers7

2

I found solution on this site but don't remember where exactly.

Objective-C:

@interface YourViewController () {
  UIImageView *navBarHairlineImageView;
}

- (void)viewWillAppear:(BOOL)animated {
  [super viewWillAppear:animated];
  navBarHairlineImageView = [self findHairlineImageViewUnder:self.navigationController.navigationBar];
  navBarHairlineImageView.hidden = YES;
}

- (UIImageView *)findHairlineImageViewUnder:(UIView *)view {
  if ([view isKindOfClass:UIImageView.class] && view.bounds.size.height <= 1.0) {
    return (UIImageView *)view;
  }
  for (UIView *subview in view.subviews) {
      UIImageView *imageView = [self findHairlineImageViewUnder:subview];
      if (imageView) {
          return imageView;
      }
  }
  return nil;
}

Swift:

class YourViewController: UIViewController {
    var navBarLine: UIImageView?

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        navBarLine = findHairlineImageViewUnderView(self.navigationController?.navigationBar)
        navBarLine?.hidden = true
    }

    func findHairlineImageViewUnderView(view: UIView?) -> UIImageView? {
        if view.isKindOfClass(UIImageView.classForCoder()) && view.bounds.height <= 1 {
            return view as? UIImageView
        }
        for subview in view.subviews {
            if let imgView = findHairlineImageViewUnderView(subview) {
                return imgView
            }
        }
        return nil
    }
}
Glen Selle
  • 3,966
  • 4
  • 37
  • 59
Alexandr Kolesnik
  • 1,929
  • 1
  • 17
  • 30
  • Hm... There seems to be some errors with the code... It's not compiling on my end and it seems that there are some misspelling involved as well... – Jae Dec 24 '15 at 15:00
1

I use this lines of code

UINavigationBar.appearance().shadowImage = UIImage()
UINavigationBar.appearance().setBackgroundImage(UIImage(named: "background"), for: .default)
Alexandr Kolesnik
  • 1,929
  • 1
  • 17
  • 30
0

Try this

    for parent in self.navigationController!.navigationBar.subviews {
 for childView in parent.subviews {
     if(childView is UIImageView) {
         childView.removeFromSuperview()
     }
 }
}

I hope this help you.

Nikunj
  • 655
  • 3
  • 13
  • 25
0

You could use this

 self.navigationController?.navigationBar.subviews[0].subviews.filter({$0 is UIImageView})[0].removeFromSuperview()
Richard Beattie
  • 293
  • 1
  • 3
  • 17
0

I didn't find any good Swift 3 solution so I am adding this one, based on Ivan Bruel answer. His solution is protocol oriented, allows to hide hairline in any view controller with just one line of code and without subclassing.

Add this code to your views model:

protocol HideableHairlineViewController {

  func hideHairline()
  func showHairline()
}

extension HideableHairlineViewController where Self: UIViewController {

  func hideHairline() {
    findHairline()?.isHidden = true
  }

  func showHairline() {
    findHairline()?.isHidden = false
  }

  private func findHairline() -> UIImageView? {
    return navigationController?.navigationBar.subviews
      .flatMap { $0.subviews }
      .flatMap { $0 as? UIImageView }
      .filter { $0.bounds.size.width == self.navigationController?.navigationBar.bounds.size.width }
      .filter { $0.bounds.size.height <= 2 }
      .first
  }

}

Then make sure view controller which doesn't need hairline conforms to HideableHairlineViewController protocol and call hideHairline().

Community
  • 1
  • 1
Boris Y.
  • 4,387
  • 2
  • 32
  • 50
0

Swift 4 version of alexandr answer

Step 1: Create property of type UIImageView?

private var navigationBarHairLine: UIImageView? 

Step 2: Create findHairlineImageViewUnderView function

This function filters through the view's subviews to find the view with the height of less than or equal to 1pt.

func findHairlineImageViewUnderView(view: UIView?) -> UIImageView? { 
    guard let view = view else { return nil } 
    if view.isKind(of: UIImageView.classForCoder()) && view.bounds.height <= 1 { 
        return view as? UIImageView 
    } 
    for subView in view.subviews { 
        if let imageView = findHairlineImageViewUnderView(view: subView) { 
            return imageView 
        } 
    } 
    return nil 
}

Step 3: Call the created function in ViewWillAppear and pass in the navigationBar. It will return the hairline view which you then set as hidden.

override func viewWillAppear(_ animated: Bool) { 
    super.viewWillAppear(animated) 
    navigationBarHairLine = findHairlineImageViewUnderView(view: navigationController?.navigationBar) 
    navigationBarHairLine?.isHidden = true 
} 
8HP8
  • 1,720
  • 3
  • 16
  • 15
0

You can subclass UINavigationBar and set the following in initializer (Swift 5):

shadowImage = UIImage()
setBackgroundImage(UIImage(), for: .default) // needed for iOS 10

E.g.:

class CustomNavigationBar: UINavigationBar {

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupViews()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setupViews()
    }


    private func setupViews() {
        shadowImage = UIImage()
        setBackgroundImage(UIImage(), for: .default) // needed for iOS 10
    }

}
Nikolay Derkach
  • 1,734
  • 2
  • 22
  • 34