I'm super new to iOS app development using Swift and Xcode. Right now, I'm trying to create a base view with a navbar for all my views instead of using a nav controller. When I started in landscape mode, the navbar looks fine, but the height looks off when I switch to portrait mode. When I start in portrait mode, landscape mode looks off where the nav bar is only half of the full length. It seems that when I change to landscape mode, no nav bar is being drawn and it used the old one... In the code below, I have an orientation listener which draws the navbar again, but it doesn't seem to work.
import UIKit
class BaseViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(BaseViewController.rotated), name: UIDevice.orientationDidChangeNotification, object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self, name: UIDevice.orientationDidChangeNotification, object: nil)
}
#objc func rotated() {
if UIDevice.current.orientation.isLandscape {
print("Landscape")
createNavBarLandscape()
} else {
print("Portrait")
createNavBarPortrait()
}
}
func createNavBarLandscape() {
let navBar = UINavigationBar(frame: CGRect(x: 0, y: 50, width: view.frame.size.width * 2, height: 24))
view.addSubview(navBar)
navBar.barTintColor = .white
let navItem = UINavigationItem(title: "logo")
let logo = UIImage(named: "logo.png")
let imageView = UIImageView(image:logo)
navItem.titleView = imageView
navBar.setItems([navItem], animated: false)
}
func createNavBarPortrait() {
let navBar = UINavigationBar(frame: CGRect(x: 0, y: 50, width: view.frame.size.width, height: 44))
view.addSubview(navBar)
navBar.barTintColor = .white
let navItem = UINavigationItem(title: "logo")
let logo = UIImage(named: "logo.png")
let imageView = UIImageView(image:logo)
navItem.titleView = imageView
navBar.setItems([navItem], animated: false)
}
}
Starting in portrait mode:
Portrait mode:
Landscape mode:
Starting in landscape mode:
Landscape mode:
Portrait mode:
Give this a try...
class MyBaseViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let navBar = UINavigationBar()
view.addSubview(navBar)
navBar.barTintColor = .white
let navItem = UINavigationItem(title: "logo")
let imageView = UIImageView()
if let logo = UIImage(named: "logo") {
imageView.image = logo
}
navItem.titleView = imageView
navBar.setItems([navItem], animated: false)
navBar.translatesAutoresizingMaskIntoConstraints = false
// respect safe area
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
navBar.topAnchor.constraint(equalTo: g.topAnchor),
navBar.leadingAnchor.constraint(equalTo: g.leadingAnchor),
navBar.trailingAnchor.constraint(equalTo: g.trailingAnchor),
])
}
}
Related
I'm getting to grips with Swift 4 and Xcode.
I can't figure out how to close a WKWebView after it has been opened!
I am using it to display a PDF document, and have added a Navigation Bar with a 'Done' button.
I have figured out how to got to close it and go to the Root UIViewController, however I want it to go to a View Controller called 'DocumentsViewController'.
Below is the code I am working with, however I believe the line I am trying to fix is:
let doneItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.done, target: nil, action: #selector(selectAction(_:)))
With selectAction being:
#objc func selectAction(_ sender:UIBarButtonItem) -> Void {
//print("Select Clicked")
self.view.window?.rootViewController?.dismiss(animated: true, completion: nil)
}
I've spent hours trying to figure this out! How can I make it so when I click the done button it closes the WKWebView and goes to the 'DocumentsViewController'?
#objc func selectAction(_ sender:UIBarButtonItem) -> Void {
//print("Select Clicked")
self.view.window?.rootViewController?.dismiss(animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
if let url = Bundle.main.url(forResource: pdfTitle, withExtension: "pdf") {
let webView = WKWebView(frame: self.view.frame)
let urlRequest = URLRequest(url: url)
webView.load(urlRequest as URLRequest)
self.view.addSubview(webView)
self.tabBarController?.tabBar.isHidden = true
let screenSize: CGRect = UIScreen.main.bounds
let navBar: UINavigationBar = UINavigationBar(frame: CGRect(x: 0, y: 0, width: screenSize.width, height: 44))
self.view.addSubview(navBar);
let navItem = UINavigationItem(title: "");
let doneItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.done, target: nil, action: #selector(selectAction(_:)))
navItem.rightBarButtonItem = doneItem;
navBar.setItems([navItem], animated: false);
// Get height of status bar (iPhone X introduced a new status bar)
let statusBarHeight = UIApplication.shared.statusBarFrame.height
// Initialize the frame
webView.frame = CGRect.init(x: 0, y: statusBarHeight, width: view.bounds.maxX, height: view.bounds.maxY)
// Set background color of status bar (optional)
self.view.backgroundColor = UIColor(red: 248/255.0, green: 248/255.0, blue: 248/255.0, alpha: 1.0)
}
Just add your new view to the navigation stack...
#objc func selectAction(_ sender:UIBarButtonItem) -> Void {
let newViewController = DocumentsViewController()
self.navigationController?.pushViewController(newViewController, animated: true)
}
I am trying to make a popover that will allow users to swipe between images (is there a better approach to this than using a popover?).
Right now to make the popover, I have spend hours googling on simply how to make a rectangle center in the screen. From the internet my code is:
// get a reference to the view controller for the popover
let popController = UIStoryboard(name: "Event", bundle: nil).instantiateViewController(withIdentifier: "carouselPopover")
// set the presentation style
popController.modalPresentationStyle = UIModalPresentationStyle.popover
let width = view.frame.width
popController.preferredContentSize = CGSize(width: width, height: 300)
// set up the popover presentation controller
popController.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.up
popController.popoverPresentationController?.delegate = self
popController.popoverPresentationController?.sourceView = self.view
popController.popoverPresentationController?.sourceRect = CGRect(x: view.bounds.midX, y: view.bounds.midY, width: 0, height: 0)
popController.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection(rawValue: 0)
// present the popover
self.present(popController, animated: true, completion: nil)
However, for the life of me I cannot understand why the popover is not centered
It only loses its center when I set the popController's preferred content size. Any thoughts?
TL:DR
I want to 1) center the popover on the screen, 2) Make the popover 1:1 ratio, and 3) make the width of the popover proportional to the width of the parent screen. How can I do that without 1000's lines of code.
You can create a custom popover class. Then you establish delegate patterns to determine if user swipes.
protocol PopoverDelegate: class {
func imageviewDidSwipe(_ popover: Popover)
}
class Popover: UIView {
weak var delegate: PopoverDelegate?
init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.white
}
func setupImage(_ image: UIImage) {
let imageView = UIView(frame: CGRect.zero)
imageView.image = image
self.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
imageView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
imageView.heightAnchor.constraint(equalToConstant: 50).isActive = true // However big you want
imageView.widthAnchor.constraint(equalToConstant: 50).isActive = true // However big you want
}
func showPopover(over view: UIView) {
view.addSubview(self)
translatesAutoResizingMaskIntoConstraints = false
centerXAnchor.contraint(equalTo: view.centerXAnchor).isActive = true
centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
heightAnchor.constraint(equalToConstant: frame.height).isActive = true
widthAnchor.constraint(equalToConstant: frame.width).isActive = true
}
}
To use...
class vC: UIViewController {
func ImageOnClick() {
// change view to where you want popover to show on top of
let popover = Popover(frame: view.frame)
popover.setupImage(image.png)
popover.delegate = self
showPopover(over: view)
}
}
extension vC: PopoverDelegate {
func imageviewDidSwipe(_ popover: Popover) {
// image swiped
}
}
I'm trying to get the black circle to go on top of the red Nav Bar but am lost for how to achieve. This is what I have so far:
override func viewDidAppear(_ animated: Bool) {
//Adding the button
let buttonImage = #imageLiteral(resourceName: "button")
var buttonView : UIImageView {
let view = UIImageView(image: buttonImage)
view.translatesAutoresizingMaskIntoConstraints = false
view.frame = CGRect(x: self.view.frame.maxX - 100, y: -100, width: 200, height: 200)
view.backgroundColor = .clear
return view
}
super.view.addSubview(buttonView)
}
Try this one. This should put the image on top of the nav bar.
override func viewDidAppear(_ animated: Bool) {
//Adding the button
navigationController?.navigationBar.addSubview(buttonView)
let navBar = navigationController?.navigationBar
// use this navBar to set your framing and constraints
let buttonImage = #imageLiteral(resourceName: "button")
var buttonView : UIImageView {
let view = UIImageView(image: buttonImage)
view.translatesAutoresizingMaskIntoConstraints = false
view.frame = CGRect(x: self.view.frame.maxX - 100, y: -100, width: 200, height: 200)
view.backgroundColor = .clear
return view
}
}
I am working on a swift project.In that i need the logo to be shown in left side of navigation bar and would like to make it globally in AppDelegate. But self.navigationitem is not detected in Appdelegate?Any Help would be appreciated as its my first project in swift.
UINavigationBar.appearance().barTintColor = Constants.templatecolor
let logoImage = UIImage.init(named: "logoImage")
let logoImageView = UIImageView.init(image: logoImage)
logoImageView.frame = CGRect(x: -40, y: 0, width: 150, height: 25)
logoImageView.contentMode = .scaleAspectFit
let imageItem = UIBarButtonItem.init(customView: logoImageView)
let negativeSpacer = UIBarButtonItem.init(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
negativeSpacer.width = -25
UINavigationItem.setLeftBarButtonItems(negativeSpacer, imageItem)
You can't set the Navigation item in AppDelegate. Navigation item is the property of UIViewController or UINavigationController and you set the UINavigationItem for each ViewControllers.
If all that you wanna achieve is setting the navigation title for all screens declare a base class call it as BaseViewController n in viewDidLoad of BaseViewController set the navigation item
class BaseViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let logoImage = UIImage.init(named: "close")
let logoImageView = UIImageView.init(image: logoImage)
logoImageView.frame = CGRect(x: -40, y: 0, width: 150, height: 25)
logoImageView.contentMode = .scaleAspectFit
let imageItem = UIBarButtonItem.init(customView: logoImageView)
let negativeSpacer = UIBarButtonItem.init(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
negativeSpacer.width = -25
self.navigationItem.leftBarButtonItems = [negativeSpacer,imageItem]
// Do any additional setup after loading the view.
}
}
Now every single viewController in your project can extend from BaseViewController
class ViewController: BaseViewController {
...
override func viewDidLoad() {
super.viewDidLoad()
}
}
Make sure you call super.viewDidLoad in viewDidLoad
Hope it helps
Please change x position of logoImageView while setting its frame.
You are sets it's value as -40 might be because of that you are not able to see that logoImage.
Change it's x position to 0.
I've added a search bar to my navigation.titleView
self.navigationItem.titleView = searchBar
There's also a BackBarButtonItem with title = ""
self.navigationItem.backBarButtonItem?.title = ""
But then there're gap between Back Button and SearchBar, like this:
I Think that the gap appears here because there's space for title of backBarButtonItem (because my title is null "" but the space still there)
So I want to ask how to omit that gap? I want to make my searchBar nearer my backBarIcon
Thank you so much!
EDIT 1:
I try to change searchBar's frame but it's not working
This is my code
//Change searchBar's frame
let titleViewFrame = (searchController.searchBar.frame)
searchController.searchBar.frame = CGRect(x: titleViewFrame.minX - 20.0, y: titleViewFrame.minY, width: titleViewFrame.width + 20.0, height: titleViewFrame.height)
override func viewDidLoad() {
super.viewDidLoad()
let container = UIView(frame: CGRect(x: 0, y: 0, width: 1000, height: 22))
let searchBar = UISearchBar()
searchBar.translatesAutoresizingMaskIntoConstraints = false
container.addSubview(searchBar)
let leftButtonWidth: CGFloat = 35 // left padding
let rightButtonWidth: CGFloat = 75 // right padding
let width = view.frame.width - leftButtonWidth - rightButtonWidth
let offset = (rightButtonWidth - leftButtonWidth) / 2
NSLayoutConstraint.activate([
searchBar.topAnchor.constraint(equalTo: container.topAnchor),
searchBar.bottomAnchor.constraint(equalTo: container.bottomAnchor),
searchBar.centerXAnchor.constraint(equalTo: container.centerXAnchor, constant: -offset),
searchBar.widthAnchor.constraint(equalToConstant: width)
])
self.navigationItem.titleView = container
}
You can't do that, there is a default space given which we cannot change if we have back button.
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
self.navigationController?.navigationBar.backIndicatorImage = UIImage(named: "back")
self.navigationController?.navigationBar.backIndicatorTransitionMaskImage = UIImage(named: "back")
self.navigationController?.navigationBar.tintColor = UIColor.lightGray
Below is the screenshot
class SearchBarContainerView: UIView {
let searchBar: UISearchBar
init(customSearchBar: UISearchBar) {
searchBar = customSearchBar
super.init(frame: CGRect.zero)
addSubview(searchBar)
}
override convenience init(frame: CGRect) {
self.init(customSearchBar: UISearchBar())
self.frame = frame
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
searchBar.frame = bounds
}
}
class MyViewController: UIViewController {
func setupNavigationBar() {
let searchBar = UISearchBar()
let searchBarContainer = SearchBarContainerView(customSearchBar: searchBar)
searchBarContainer.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 44)
navigationItem.titleView = searchBarContainer
}
}