Swift: Set UIBarButtonItem as source for popover WITHOUT tap? - swift

I want to show a popover on iPad as soon as my view loads having as source a button on the top right corner.
The popover displays properly on button tap, but I'm having trouble finding a way to display it without the button tap, when the page first loads. Is this possible?
This is what I have:
func displayOptions(sourceButton: UIBarButtonItem)
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
if let popoverController = actionSheet.popoverPresentationController {
var rect = CGRect(x: 0, y: 0, width: 0, height: 0)
popoverController.barButtonItem = sourceButton
popoverController.sourceRect = rect
popoverController.permittedArrowDirections = .up
}
viewController.present(actionSheet, animated: true, completion: nil)
}
This code works properly on button tap, but the app crashes if I try to call this function from viewDidLoad or viewDidAppear:
You must provide location information for this popover through the alert controller's popoverPresentationController. You must provide either a sourceView and sourceRect or a barButtonItem.

Try using UIPopoverPresentationControllerDelegate
Code example:
viewDidLoad
let buttonCustom = UIBarButtonItem(title: "Custom", style: .done, target: self, action: #selector(ViewController.customBarButtonAction))
self.navigationItem.rightBarButtonItem = buttonCustom
displayOptions()
displayOptions
func displayOptions() {
let actionSheet = UIAlertController(title: "Title", message: "Message", preferredStyle: .actionSheet)
if let popoverController = actionSheet.popoverPresentationController {
let rect = CGRect(x: 0, y: 0, width: 0, height: 0)
popoverController.sourceRect = rect
popoverController.sourceView = self.view
popoverController.permittedArrowDirections = .up
popoverController.delegate = self
}
self.present(actionSheet, animated: true, completion: nil)
}
using UIPopoverPresentationControllerDelegate
func prepareForPopoverPresentation(_ popoverPresentationController: UIPopoverPresentationController) {
popoverPresentationController.barButtonItem = self.navigationItem.rightBarButtonItem
}

You need to provide the sourceView.
UIAlertController must have a title, a message or an action to display.
// Create a bar button item in ViewDidLoad.
let button1 = UIBarButtonItem(image: UIImage(named: "imagename"), style: .plain, target: self, action: Selector("action"))
self.navigationItem.rightBarButtonItem = button1
// Call in ViewDidLoad
displayOptions(sourceButton: button1)
func displayOptions(sourceButton: UIBarButtonItem) {
let actionSheet = UIAlertController(title: "title", message: "message", preferredStyle: .actionSheet)
if let popoverController = actionSheet.popoverPresentationController {
let rect = CGRect(x: 0, y: 0, width: 0, height: 0)
popoverController.barButtonItem = sourceButton
popoverController.sourceRect = rect
popoverController.sourceView = self.view
popoverController.permittedArrowDirections = .up
}
self.present(actionSheet, animated: true, completion: nil)
}

Related

Present a Popover view from UIToolbar barButtonItem

Screenshot of the gap
I need to present a popover view from a UIBarbuttonItem in a UIToolbar. But there exists a gap between the popover view and the toolbar.
private func addToolbar() {
let toolBar = UIToolbar(frame: CGRect(x: 0, y: 0, width: view.frame.size.width, height: 50))
let item1 = UIBarButtonItem(image: UIImage(systemName: "pencil"), style: .plain, target: self, action: #selector(item1Pressed(_:)))
let item2 = UIBarButtonItem(image: UIImage(systemName: "house"), style: .plain, target: self, action: #selector(item2Pressed(_:)))
toolBar.sizeToFit()
toolBar.items = [item1, item2]
textField.inputAccessoryView = toolBar
}
class SearchViewController: UIViewController {
let searchBar = UISearchBar()
weak var viewControllerDelegate: SearchViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
setupView()
setupSearchbar()
}
private func setupView() {
view.backgroundColor = .white
navigationController?.navigationBar.prefersLargeTitles = true
self.title = "Search"
}
private func setupSearchbar() {
searchBar.sizeToFit()
searchBar.placeholder = "Search test"
searchBar.showsCancelButton = true
searchBar.delegate = self
self.navigationItem.titleView = searchBar
}
}
I tried setting the sourceRect of the presented popover view, but the popover view doesn't move down below a certain point.
#objc private func item1Pressed(_ sender: UIBarButtonItem) {
let vc = SearchViewController()
vc.viewControllerDelegate = self
let navVC = UINavigationController(rootViewController: vc)
navVC.modalPresentationStyle = .popover
navVC.popoverPresentationController?.delegate = self
navVC.popoverPresentationController?.permittedArrowDirections = .any
navVC.preferredContentSize = CGSize(width: 500, height: 200)
navVC.popoverPresentationController?.sourceItem = sender
var location = CGPoint(x: 0, y: 0)
if let barItemView = sender.value(forKey: "view") as? UIView {
let barFrame = barItemView.frame
let rect = barItemView.convert(barFrame, to: view)
location = rect.origin
}
navVC.popoverPresentationController?.sourceRect = CGRect(x: location.x, y: location.y+100, width: 0, height: 0)
present(navVC, animated: true)
}

Navigation Bar don't charge correctly the first time

I am developing an app that contains a login and when logging in for the first time the views that go after logging do not load well the navigation bar, when opening the app for the second time and already with the credentials previously entered, this time if they load well the navigation bar.
Does anyone know why this happens?
my viewWillAppear is like this
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 38, height: 38))
imageView.contentMode = .scaleAspectFit
let image = UIImage(named: "img_header")
imageView.image = image
navigationItem.titleView = imageView
//let image_home = UIImage(named: "ic_home")
//let homeButton = UIBarButtonItem(image: image_home, style: UIBarButtonItemStyle.done, target: self, action: #selector(self.cancelMetod2))
// navigationItem.leftBarButtonItem = homeButton
let color_primary: UIColor = val_colores.hexStringToUIColor(val_colores.colorPrimary)
let color_background_primary: UIColor = val_colores.hexStringToUIColor(val_colores.ColorGrisClaro2)
//icono more
let imagen_more = UIImage (named: "ic_more_menu")!
let buttonMore: UIBarButtonItem = UIBarButtonItem(image: imagen_more, style: UIBarButtonItemStyle.done,target: self, action: #selector(self.moreMetod))
self.navigationItem.rightBarButtonItem = buttonMore
//background color
self.navigationController?.navigationBar.barTintColor = color_primary
//back button color
UIBarButtonItem.appearance().tintColor = color_background_primary
//Since iOS 7.0 UITextAttributeTextColor was replaced by NSForegroundColorAttributeName
//title color
UINavigationBar.appearance().titleTextAttributes = [NSAttributedStringKey.foregroundColor: color_background_primary]
//color in carrier, hour, battery
UINavigationBar.appearance().barStyle = UIBarStyle.black
self.navigationController!.navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor: color_background_primary]
self.navigationController!.navigationBar.barStyle = UIBarStyle.black
self.navigationController!.navigationBar.tintColor = color_background_primary
}

dismissing the keyboard on UIAlertController UITextField

normally view.endEditing(true) always removes the keyboard in swift on iOS.
however this is not always true, like when the UITextField is part of a self.present()'ed modal view i.e. a form inside a UIAlertController
Example
let alert = UIAlertController(title: _title, message: "Choose", preferredStyle: .alert)
let numberLabel = UILabel(frame: CGRect(x:10, y:0, width:90, height:20))
numberLabel.textColor = .black
numberLabel.textAlignment = NSTextAlignment.left
numberLabel.font = UIFont(name: titleLabel.font.fontName, size: 16)
numberLabel.font = UIFont.boldSystemFont(ofSize: titleLabel.font.pointSize)
numberLabel.text = "Quantity"
let quantityField = UITextField(frame: CGRect(x:100, y:0, width:140, height:25))
quantityField.layer.borderWidth = 1
quantityField.keyboardType = .numberPad
quantityField.layer.borderColor = UIColor.lightGray.cgColor
quantityField.delegate = self
self.addDoneButtonOnKeyboard(textField: quantityField)
self.present(alert, animated: true, completion: nil)
now in this self.addDoneButtonOnKeyboard method I have this nifty code
func addDoneButtonOnKeyboard(textField: UITextField){
let doneToolbar: UIToolbar = UIToolbar(frame: CGRect(x:0, y:0, width:320, height:50))
doneToolbar.barStyle = UIBarStyle.default
let flexSpace = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.flexibleSpace, target: nil, action: nil)
let done: UIBarButtonItem = UIBarButtonItem(title: "Done", style: UIBarButtonItemStyle.done, target: self, action: #selector(ViewControllerDisplay.doneButtonAction(_:)))
let items = NSMutableArray()
items.add(flexSpace)
items.add(done)
doneToolbar.items = items as? [UIBarButtonItem]
doneToolbar.sizeToFit()
textField.inputAccessoryView = doneToolbar
}
#objc func doneButtonAction(_ btn: UIBarButtonItem){
view.endEditing(true)
}
the purpose of all this is so that a "Done" keyboard button appears. It does. In fact it looks great, I can also capture the doneButtonAction() event which is great, but I am only capturing the UIBarButtonItem so I can't use UITextField.resignFirstResponder() method, even though I know it will work. I have to use "view"
What I want to do is call alert.endEditting(true) but I've tried cycling through the subviews, screening for any occurrences of UIAlertController but self.present doesn't work that way
Can anyone help me clear this keyboard off the alert view?
I fixed it by getting the topMost view Controller, and calling endEditing(true) I think this question should remain up it's quite a useful titbit for someone one day
#objc func doneButtonAction(_ btn: UIBarButtonItem){
if var topController = UIApplication.shared.keyWindow?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
topController.view.endEditing(true)
}
}

Back Button Image - what is it called in Swift?

I am trying to use Swift's internal back button image.
I have asked this question before and got the technically correct answer that it inherits it from the previous View, BUT if I insert this code you can control the back button on the current View.
// Takeover Back Button
self.navigationItem.hidesBackButton = false
let newBackButton = UIBarButtonItem(title: "<", style: .Plain, target: self, action: "segueBack")
navigationItem.leftBarButtonItem = newBackButton
That gives me a <, "ABC" would give me ABC etc but how do I trigger Swift to put up it's internal Back Button image. The code below doesn't work but I would have thought is along the right lines.
let backImg: UIImage = UIImage(named: "BACK_BUTTON_DEFAULT_ICON")!
navigationItem.leftBarButtonItem!.setBackgroundImage(backImg, forState: .Normal, barMetrics: .Default)
Has anyone worked out how to do this?
Try to add custom view as back button like as
var backButton = UIButton(frame: CGRectMake(0, 0, 70.0, 70.0))
var backImage = UIImage(named: "backBtn")
backButton.setImage(backImage, forState: UIControlState.Normal)
backButton.titleEdgeInsets = UIEdgeInsetsMake(10.0, 10.0, 10.0, 0.0)
backButton.setTitle("Back", forState: UIControlState.Normal)
backButton.addTarget(self, action: "buttonPressed", forControlEvents: UIControlEvents.TouchUpInside)
var backBarButton = UIBarButtonItem(customView: backButton)
var spacer = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FixedSpace, target: nil, action: nil)
spacer.width = -15
self.navigationItem.leftBarButtonItems = [spacer,backBarButton]
It will look same as iOS back button
I struggled with this question for a while. Finally I got the back image with the following code:
let backImage = navigationController?.navigationBar.subviews[2].subviews[0].subviews[0].subviews[0] as! UIImageView).image
Before run the code above, make sure the back button is showing. Then you can save backImage to anywhere you want.
Here is the backImage I got.
Here is my solution:
override func viewDidLoad() {
...
let buttonBack = UIBarButtonItem(image: UIImage(named: "backButton"), style: .plain, target: self, action: #selector(buttonSavePressed(_:)))
self.navigationItem.leftBarButtonItem = buttonBack
let backButton = UIButton(frame: CGRect(x: 0, y: 0, width: 24.0, height: 24.0))
let backImage = UIImage(named: "backButton")
backButton.setImage(backImage, for: .normal)
backButton.setTitle("Back", for:.normal)
if #available(iOS 13.0, *) {
backButton.setTitleColor(.link, for: .normal)
} else {
backButton.setTitleColor(.blue, for: .normal)
}
backButton.addTarget(self, action:#selector(buttonSavePressed(_:)), for: .touchUpInside)
let backBarButton = UIBarButtonItem(customView: backButton)
let spacer = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
spacer.width = -15
self.navigationItem.leftBarButtonItems = [spacer,backBarButton]
}
#objc func buttonBackPressed(_ sender: Any) {
...
}
If you want to get the default back button image, the arrow is a class of type _UINavigationBarBackIndicatorView.
Here follows the hack,
UIImage *imgViewBack ;
for (UIView *view in self.navigationController.navigationBar.subviews) {
// The arrow is a class of type _UINavigationBarBackIndicatorView. This is not any of the private methods, so I think
// this is fine for the AppStore...
if ([NSStringFromClass([view class]) isEqualToString:#"_UINavigationBarBackIndicatorView"]) {
// Set the image from the Default BackBtn Imageview
UIImageView *imgView = (UIImageView *) view;
if(imgView){
imgViewBack = imgView.image ;
}
}
}
Try this to replace the back button image:
let back_image = UIImage(named: "btn_back")
self.navigationBar.backIndicatorImage = back_image
self.navigationBar.backIndicatorTransitionMaskImage = back_image
If you don't like to have the "Back" title you can add this too:
self.navigationBar.topItem?.title = ""

Swift, Add a navigation controller in custom activity indicator

My Custom Activity indicator is not overlapping Navigation controller.
Below is my code
func showActivityIndicator(uiView: UIView) {
container.frame = uiView.frame
container.center = uiView.center
container.backgroundColor = UIColorFromHex(0xffffff, alpha: 0.1)
var loadingView: UIView = UIView()
loadingView.frame = CGRectMake(0, 0, self.view.frame.width, self.view.frame.height)
loadingView.center = uiView.center
loadingView.backgroundColor = UIColorFromHex(0x444444, alpha: 0.5)
loadingView.clipsToBounds = true
loadingView.layer.cornerRadius = 10
var imageData = NSData(contentsOfURL: NSBundle.mainBundle()
.URLForResource("synch-loader", withExtension: "gif")!)
let try = UIImage.animatedImageWithData(imageData!)
var imageView = UIImageView(image: try)
imageView.center = uiView.center
imageView.frame = CGRect(x: uiView.frame.width/4, y: uiView.frame.height/2, width: 500, height: 15)
loadingView.addSubview(imageView)
container.addSubview(loadingView)
uiView.addSubview(container)
actInd.startAnimating()
}
func navigationHandling()
{
if self.navigationController != nil {
self.navigationController?.navigationBar.tintColor = utility.uicolorFromHex(0x70B420)
self.navigationController?.navigationBar.barTintColor = utility.uicolorFromHex(0x70B420)
self.navigationController?.navigationBarHidden = false
self.navigationItem.hidesBackButton = true
}
sortingBtn = UIBarButtonItem(image: sortingImg, style: UIBarButtonItemStyle.Done, target: self, action: Selector("sortingPressed:"))
menuBtn = UIBarButtonItem(image: menuImg, style: UIBarButtonItemStyle.Plain, target: self, action : nil)
sortingBtn.tintColor = UIColor.whiteColor()
menuBtn.tintColor = UIColor.whiteColor()
var buttons : NSArray = [menuBtn,sortingBtn]
self.navigationItem.rightBarButtonItems = buttons as [AnyObject]
self.navigationItem.setRightBarButtonItems([menuBtn,sortingBtn], animated: true)
networkLabel.hidden = true
}
I just want to overlap the view on navigation controller so that it don't looks ugly.
I appreciate help!
let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate
let window = appDelegate?.window
window.addSubview("yourActivityIndicator")
Here add the activity indicator to the window, or pass the window in as the view.
let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate
let window = appDelegate?.window
showActivityIndicator(window)
The uiView you're passing as parameter to this method
func showActivityIndicator(uiView: UIView)
is your ViewController's view or your UINavigationController's view?
Because if it's your UIViewController's view your custom loading will take it's size and origin, that is under your NavigationBar