I have done code like this but it's open QLPreviewController but
Share button was not disabled. I have tried different things but it's
not worked.
- Or You Can Suggest me any different Preview Controller in which i can Disable Share button.
qlViewController = QLPreviewController()
qlViewController.navigationItem.rightBarButtonItem = nil
qlViewController.delegate = self
qlViewController.dataSource = self
func numberOfPreviewItemsInPreviewController(controller: QLPreviewController) -> Int {
return 1
}
func previewController(controller: QLPreviewController, previewItemAtIndex index: Int) -> QLPreviewItem {
controller.navigationItem.rightBarButtonItem = nil
return fileUrlToOpen
}
Tested on iOS 15, may change with next updates.
QLPreviewController now embeds a UINavigationController which then has a root controller with a standard navigationItem that we can tweak.
guard let navigationItem = (aQLPreviewController.children.first as? UINavigationController)?.viewControllers.first?.navigationItem else
{
// Not iOS 15 or the QLPreviewController implementation has changed
return
}
// Do whatever you want with the navigationItem
navigationItem.rightBarButtonItem?.isEnabled = false
// or navigationItem.rightBarButtonItem = nil
UPD: This DOESN'T work anymore
There is an example of how to hide the right share button
final class AttachmentQuickLookVC: QLPreviewController {
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
self.navigationItem.rightBarButtonItems = [UIBarButtonItem]()
}
}
Also, u can do something like this if u want to customize back button:
final class AttachmentQuickLookVC: QLPreviewController {
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
let backButton = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 35))
backButton.contentEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
backButton.contentHorizontalAlignment = .left
backButton.setImage(UIImage(named: "ic_back"), for: .normal)
let barButton = UIBarButtonItem(customView: backButton)
self.navigationItem.leftBarButtonItems = [barButton]
self.navigationItem.hidesBackButton = true
}
}
Related
I am trying to expand the touch area of the filter button. When the green filter indicator appears it reduced the size. How can I fix this?
private func setupView() {
self.snp.makeConstraints { make in
make.height.equalTo(40)
}
addArrangedSubview(filtersOn)
addArrangedSubview(filterButton)
filtersOn.snp.makeConstraints { make in
make.centerY.equalTo(filterButton)
make.trailing.equalTo(filterButton.snp.leading).offset(50)
}
}
}
private func showFilterButton() {
guard let parent = parent as? MyDealersMasterViewController else { return }
let filterButton = FilterBarButtonItem(position: .right)
filterButton.filterButton.contentEdgeInsets = UIEdgeInsets(top: 60, left: 60, bottom: 60, right: 0)
filterButton.filterButton.addTarget(self, action: #selector(filterDealerList), for: .touchUpInside)
filterButton.backgroundColor = .red
parent.navigationItem.rightBarButtonItem?.customView = filterButton
}
In my app, I have a button which when clicked should display time picker with toolbar on it. Most of examples I saw added toolbar as an inputAccessoryView on text field, but in my case I don't have a text field.
So, I created a custom view which has date time picker and toolbar and I am adding that view as a subview to my controller's view, but I don't see the custom view on the app.
Below is the controller code for button clicked :
func buttonClicked(date: Date) {
let timePicker = EditTimeHelper.createTimePickerAndToolbar(displayDate: date)
self.view.addSubview(timePicker)
}
Code for custom view in separate EditTimeHelper class:
static func createTimePickerAndToolbar(displayDate: Date) -> UIView {
let pickerView = UIView(frame: CGRect(x: 0, y: UIScreen.main.bounds.height - 300, width: UIScreen.main.bounds.width, height: 300))
let timePicker = createTimePicker(displayDate: displayDate)
pickerView.addSubview(timePicker)
let toolbar = createUIToolBar()
pickerView.addSubview(toolbar)
return pickerView
}
static func createTimePicker(displayDate: Date) -> UIDatePicker {
let timePicker:UIDatePicker = UIDatePicker()
timePicker.datePickerMode = UIDatePicker.Mode.time
timePicker.date = displayDate
timePicker.minuteInterval = 15
if #available(iOS 13.4, *) {
timePicker.preferredDatePickerStyle = .wheels
} else {
// Fallback on earlier versions where time picker is wheels style by default.
}
timePicker.addTarget(self, action: #selector(timeChanged(_:)), for: UIControl.Event.valueChanged)
timePicker.backgroundColor = .white
timePicker.frame = CGRect(x: 0, y: UIScreen.main.bounds.height - 200, width: UIScreen.main.bounds.width, height: 200)
return timePicker
}
private static func createUIToolBar() -> UIToolbar {
let pickerToolbar = UIToolbar()
pickerToolbar.autoresizingMask = .flexibleHeight
//customize the toolbar
pickerToolbar.barStyle = .default
pickerToolbar.barTintColor = UIColor.black
pickerToolbar.backgroundColor = UIColor.white
pickerToolbar.isTranslucent = false
pickerToolbar.frame = CGRect(x: 0, y: UIScreen.main.bounds.height - 300, width: UIScreen.main.bounds.width, height: 100)
// add buttons
let cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelBtnClicked(_:)))
cancelButton.tintColor = UIColor.white
let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneBtnClicked(_:)))
doneButton.tintColor = UIColor.white
//add the items to the toolbar
pickerToolbar.items = [cancelButton, flexSpace, doneButton]
return pickerToolbar
}
#objc func timeChanged(_ sender: UIDatePicker) {
}
#objc func cancelBtnClicked(_ button: UIBarButtonItem?) {
}
#objc func doneBtnClicked(_ button: UIBarButtonItem?) {
}
Any idea what am I doing wrong and not seeing custom view?
If I call EditTimeHelper.createTimePicker(displatDate: date), then I see the time picker, but I want to add toolbar on top of it.
When I debug this code, I do see time picker and toolbar as custom view's subviews, but I just don't see them on the app.
The reason why you can't see the picker and the tool bar is because you have positioned the time picker and the tool bar incorrectly. Notice these two lines:
timePicker.frame = CGRect(x: 0, y: UIScreen.main.bounds.height - 200, width: UIScreen.main.bounds.width, height: 200)
// and
pickerToolbar.frame = CGRect(x: 0, y: UIScreen.main.bounds.height - 300, width: UIScreen.main.bounds.width, height: 100)
Since these are subviews of the pickerView, the coordinates are relative to the top left corner of pickerView, not the top left corner of the screen. You should instead do
timePicker.frame = CGRect(x: 0, y: 100, width: UIScreen.main.bounds.width, height: 200)
// and
pickerToolbar.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 100)
Now you should see the tool bar and the time picker.
There are other problems with your code, however. First, timeChanged, cancelBtnClicked and doneBtnClicked won't be called. You have added self as the target for the bar button items and the picker, but since you are in a static method, self refers to the class itself. When the user presses the done button, it would try to call a method called doneBtnClicked on the class, rather than a particular instance. But the class doesn't have such a method! The doneBtnClicked you have declared is an instance method, available on instances only.
Second, you are giving these views fixed positions. This means that the layout will look very weird when the user rotates the screen. Just use AutoLayout!
You can make timeChanged, cancelBtnClicked and doneBtnClicked all static too, but a much better way is to just create a custom UIView subclass. Here is an example, as a starting point:
class TimePickerToolBarView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
let timePicker = createTimePicker()
addSubview(timePicker)
let toolBar = createUIToolBar()
addSubview(toolBar)
timePicker.translatesAutoresizingMaskIntoConstraints = false
toolBar.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
toolBar.heightAnchor.constraint(equalToConstant: 44),
toolBar.topAnchor.constraint(equalTo: topAnchor),
toolBar.leftAnchor.constraint(equalTo: leftAnchor),
toolBar.rightAnchor.constraint(equalTo: rightAnchor),
timePicker.leftAnchor.constraint(equalTo: leftAnchor),
timePicker.rightAnchor.constraint(equalTo: rightAnchor),
timePicker.bottomAnchor.constraint(equalTo: bottomAnchor),
timePicker.topAnchor.constraint(equalTo: toolBar.bottomAnchor),
])
}
private func createTimePicker() -> UIDatePicker {
let timePicker:UIDatePicker = UIDatePicker(frame: .zero)
timePicker.datePickerMode = UIDatePicker.Mode.time
timePicker.minuteInterval = 15
if #available(iOS 13.4, *) {
timePicker.preferredDatePickerStyle = .wheels
} else {
// Fallback on earlier versions where time picker is wheels style by default.
}
timePicker.addTarget(self, action: #selector(timeChanged(_:)), for: UIControl.Event.valueChanged)
timePicker.backgroundColor = .white
return timePicker
}
private func createUIToolBar() -> UIToolbar {
let pickerToolbar = UIToolbar(frame: .zero)
//customize the toolbar
pickerToolbar.barStyle = .default
pickerToolbar.barTintColor = UIColor.black
pickerToolbar.backgroundColor = UIColor.white
pickerToolbar.isTranslucent = false
// add buttons
let cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelBtnClicked(_:)))
cancelButton.tintColor = UIColor.white
let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneBtnClicked(_:)))
doneButton.tintColor = UIColor.white
//add the items to the toolbar
pickerToolbar.items = [cancelButton, flexSpace, doneButton]
return pickerToolbar
}
#objc func timeChanged(_ sender: UIDatePicker) {
}
#objc func cancelBtnClicked(_ button: UIBarButtonItem?) {
}
#objc func doneBtnClicked(_ button: UIBarButtonItem?) {
}
}
I created a navigation bar which I'm trying to call in another view controller. I set it up by calling the methods which I separated into left, center and right buttons. In my other controller I call the navbarcontroller and try and call the method for which i setup the navigation toolbar. Nothing happens, however there is no crash.
import UIKit
class NavBarController : UIViewController{
var screenSize: CGRect!
override func viewDidLoad() {
super.viewDidLoad()
setupNavigationBarItems()
setupToolBarItems()
self.navigationController?.isToolbarHidden = false
self.view!.backgroundColor = .white
}
and my method for the navigation bar is this
func setupNavigationBarItems() {
setupCenterNavButton()
setupLeftNavButton()
setupRightNavButton()
}
func showCalendarController() {
let navController = CalendarController()
self.present(navController, animated: true, completion: nil)
} //connect bottom bar buttons to controller
func showEventsController() {
let navController = EventsController()
self.present(navController, animated: true, completion: nil)
} //connect bottom bar buttons to controller
func setupNavigationBarItems() {
setupCenterNavButton()
setupLeftNavButton()
setupRightNavButton()
} // top bar button setup
private func setupCenterNavButton() {
let buttonFrame = UIView(frame: CGRect(x: 0, y: 0, width: 165,
height: 20))
mainFeedButton.frame = CGRect(x: 0,y: 0, width: 80,height: 20) as
CGRect
mainFeedButton.backgroundColor = UIColor.blue
peekFeedButton.frame = CGRect(x: 85,y: 0, width: 80,height: 20) as
CGRect
peekFeedButton.backgroundColor = UIColor.blue
buttonFrame.addSubview(mainFeedButton)
buttonFrame.addSubview(peekFeedButton)
navigationItem.titleView = buttonFrame
} //center bar buttons / action setup
private func setupLeftNavButton() {
navigationItem.leftBarButtonItem = UIBarButtonItem(customView:
favoriteButton)
}// left bar buttons / action setup
private func setupRightNavButton() {
navigationItem.rightBarButtonItem = UIBarButtonItem(customView:
moreButton)
} //right bar buttons / action setup
lazy var mainFeedButton: UIButton! = {
let button = UIButton(type: .custom) // button type
button.setTitle("Main",for: .normal) //button title
button.sizeToFit() // size button to fit the title
var frame = button.frame //create frame to manipulate the body
button.frame = CGRect(x: 0, y: 0, width: 100, height: 40)
button.addTarget(self, action:
#selector(self.showMainFeedController),
for: .touchUpInside)
return button
}() //mainFeed button connected to Feed Controller
lazy var peekFeedButton: UIButton! = {
let button = UIButton(type: .custom) //button type
button.setTitle("Spy",for: .normal) //button title
button.sizeToFit() // size button to fit the title
var frame = button.frame //create frame to manipulate the body
button.frame = CGRect(x: 20, y: 0, width: 100, height: 40)
button.addTarget(self, action:
#selector(self.showSpyFeedController),
for: .touchUpInside)
return button
}()//peekFeed button frame and action setup
lazy var favoriteButton: UIButton! = {
let button = UIButton(type: .system) //default button with blue
text
button.setImage(#imageLiteral(resourceName:
"star").withRenderingMode(.alwaysOriginal), for: .normal)
button.contentMode = .scaleAspectFit
button.frame = CGRect(x: 0, y: 0, width: 24, height: 24)
button.addTarget(self, action: #selector(favoriteButton_tapped),
for: .touchUpInside)
return button
}() //favorites button frame and action setup
lazy var moreButton: UIButton! = {
let button = UIButton(type: .system) //default button with blue
text
button.setImage(#imageLiteral(resourceName:
"more").withRenderingMode(.alwaysOriginal), for: .normal)
button.contentMode = .scaleAspectFit
button.frame = CGRect(x: 0, y: 0, width: 24, height: 24)
button.addTarget(self, action: #selector(moreButton_tapped),
for: .touchUpInside)
return button
}() //more button frame and action setup
func showMainFeedController() {
let navController = MainFeedController()
self.present(navController, animated: true, completion: nil)
} //mainFeed button connected to Feed Controller
func showSpyFeedController() {
let navController = SpyFeedController()
self.present(navController, animated: true, completion: nil)
}//peekFeed button connected to SpyFeedController
func favoriteButton_tapped(sender: UIButton) {
print("You touched this!")
}
func moreButton_tapped(sender: UIButton) {
print("You touched this!")
}
}
I then try and call the function by setupNavigationBarItems() like this
import UIKit
class EventsController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
let navbar = NavBarController()
navbar.setupNavigationBarItems()
self.navigationController?.isToolbarHidden = false
self.view.backgroundColor = .white
}
}
I'm not sure if this a valid way. I'm still kinda new to all of this.
It's not clear what you expect to happen, but here's what does happen:
let navbar = NavBarController()
A completely new NavBarController object is created.
navbar.setupNavigationBarItems()
That NavBarController object's setupNavigationBarItems is called.
self.navigationController?.isToolbarHidden = false
self.view.backgroundColor = .white
Your code comes to an end. navbar was a local variable, so the NavBarController object vanishes in a puff of smoke. The end. This object was created and configured to no purpose.
I remember my first month in iOS way back 2015 :D, didn't have any knowledge in OOP, I didn't know too how to pass a data to another screen or class.
Anyways, you DO NOT create a new instance of your NavBarController class in your EventsController. If you want to talk to your NavBarController from your EventsController, then you will need a reference that is currently alive. You can also use delegate (search for that later).
So before you show or present your EventsController from your NavBarController, pass your current NavBarController instance to the next screen which is EventsController. BUT FIRST, you need to declare a variable in your EventsController, correct? :)
Declare a variable with a type of NavBarController inside your EventsController class, like so:
var navBarController: NavBarController!
Then in this piece of code of yours, pass your self (the NavBarController instance) to the EventsController class before showing or presenting, take note that you mistakenly gave a wrong name to your EventsController new instance, so I renamed it:
func showEventsController() {
let eventsController = EventsController()
eventsController.navBarController = self // THIS :)
self.present(eventsController, animated: true, completion: nil)
}
Lastly, instead of this:
let navbar = NavBarController()
navbar.setupNavigationBarItems()
Make use of your declared variable, like so:
self.navBarController.navbar.setupNavigationBarItems()
Hope this helps! :)
I have a UISearchBar in my application. When I press a button to "open" the searchbar does a new view appear. But the problem is that the NavigationController changes and the UISearchBar disappear. How can I do so I can keep the current NavigationController with my searchbar even if a new view appear. (So I still searching when the new view appear)
P.s my code is not the best and I´m not using Storyboard!
class HomeController: UICollectionViewController, UICollectionViewDelegateFlowLayout, UISearchBarDelegate {
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.backgroundColor = UIColor.white
setupNavigationBar()
}
Here is the new view that appear:
class UserSearchController: UICollectionViewController, UISearchBarDelegate {
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.backgroundColor = UIColor.blue
}
}
And here is the whole searchbar code:
import UIKit
var searchBar = UISearchBar()
var searchBarButtonItem: UIBarButtonItem?
var logoImageView: UIImageView!
extension HomeController {
func setupNavigationBar() {
let button = UIButton(type: .system)
button.setImage(#imageLiteral(resourceName: "search"), for: .normal)
button.addTarget(self, action: #selector(showSearchBar), for: .touchUpInside)
button.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
let barButton = UIBarButtonItem(customView: button)
self.navigationItem.rightBarButtonItem = barButton
let logoImage = UIImage(named: "home")!
logoImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: logoImage.size.width, height: logoImage.size.height))
logoImageView.image = logoImage
navigationItem.titleView = logoImageView
searchBar.delegate = self
searchBar.searchBarStyle = UISearchBarStyle.minimal
searchBar.placeholder = "Search"
searchBar.barTintColor = UIColor.gray
searchBarButtonItem = navigationItem.rightBarButtonItem
}
func showSearchBar() {
let layout = UICollectionViewFlowLayout()
let userSearchController = UserSearchController(collectionViewLayout: layout)
self.navigationController?.pushViewController(userSearchController, animated: true)
searchBar.alpha = 0
navigationItem.titleView = searchBar
navigationItem.setLeftBarButton(nil, animated: true)
navigationItem.rightBarButtonItem = nil
searchBar.showsCancelButton = true
UIView.animate(withDuration: 0.5, animations: {
self.searchBar.alpha = 1
}, completion: { finished in
self.searchBar.becomeFirstResponder()
})
}
public func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
hideSearchBar()
}
func hideSearchBar() {
navigationItem.setRightBarButton(searchBarButtonItem, animated: true)
logoImageView.alpha = 0
UIView.animate(withDuration: 0.3, animations: {
self.navigationItem.titleView = self.logoImageView
self.logoImageView.alpha = 1
}, completion: { finished in
})
}
}
The reason why your search bar is only visible on your first View Controller is because you are using the View Controller's titleView property. Each UIViewController has it's own titleView property, so if you push a View Controller onto your first VC, it will also need to have the titleView property set to a search bar view with the required configuration.
I think you can create a base class, add UISearchBar above the base class, and then you want the current controller with UISearchBar to inherit your base class
I'm trying to put a button at the middle of the navigation bar, it will show a list when I touch it (I added a pure UIView here instead of a UITabeView to just make the code simpler) . And then the additional view will be removed when I touch anywhere else. So I add a background view whose size is the same as the screen to response my touch. Although it still behind the navigation bar.
Here is my question:
Is this a good implementation?
class ViewController: UIViewController {
var optionView: UIView!
var backgroundView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton(type: .system)
button.addTarget(self, action: #selector(ViewController.titleButtonTapped), for: .touchUpInside)
button.frame = CGRect(x: 0, y: 0, width: 20, height: 30)
button.backgroundColor = .red
navigationItem.titleView = button
}
func titleButtonTapped() {
backgroundView = UIView(frame: UIScreen.main.bounds)
backgroundView.backgroundColor = .clear
let gesture = UITapGestureRecognizer(target: self, action: #selector(ViewController.handleGesture)) // add this gesture to response my touch
backgroundView.addGestureRecognizer(gesture)
view.addSubview(maskView)
optionView = UIView(frame: CGRect(x: -40, y: 30, width: 100, height: 100)) // x = button.wdith / 2 - optionView.width / 2
optionView.backgroundColor = .red
navigationItem.titleView?.addSubview(alertView)
}
func handleGesture() {
optionView.removeFromSuperview()
backgroundView.removeFromSuperview()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Now it looks like the following.
Edit:
The following is my implementation of a popover view.
func buttonTapped() {
let popoverViewController = UIViewController()
popoverViewController.preferredContentSize = CGSize(width: 300, height: 300)
popoverViewController.modalPresentationStyle = .popover
let presentationController = popoverViewController.popoverPresentationController!
presentationController.delegate = self
presentationController.sourceView = view
presentationController.sourceRect = CGRect(x: 100, y: 100 , width: 100, height: 100)
present(popoverViewController, animated: true, completion: nil)
}
// delegate
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
It's a little bit different frrom the Apple documentation. They recommended that we'd better configure the presentation controller after calling present(:animated: completion:) method. But it doesn't work if I don't configure it before presentation. Maybe, because I set the delegate.
Configuring the popover presentation controller after calling present(_:animated:completion:) might seem counter-intuitive but UIKit does not create a presentation controller until after you initiate a presentation. In addition, UIKit must wait until the next update cycle to display new content onscreen anyway. That delay gives you time to configure the presentation controller for your popover.
For using a popover or not, it depends on the purpose of this pop over view. If it has lots of information, it will be better to separate it out to another view controller and make segue to it on button click. This will provides user the full screen to look at whatever it is.
For me, adding a button at the center of a navigation bar is not usual. You have to inform me about it for me to click on it.
In conclusion:
If you want a popover view to tell user hints or show them something, it will be better to use UIPopoverPresentationController so that you don't need to care about the styles.
If you want another view to show data, list of pictures etc, it will be better to use a segmented control or another view controller
var optionView: UIView!
var backgroundView: UIView!
override func viewDidLoad()
{
super.viewDidLoad()
self.view.backgroundColor = UIColor.white
let button = UIButton(type: .system)
button.addTarget(self, action: #selector(ViewController.titleButtonTapped), for: .touchUpInside)
button.frame = CGRect(x: 0, y: 0, width: 20, height: 30)
button.backgroundColor = .red
navigationItem.titleView = button
}
func titleButtonTapped()
{
if backgroundView == nil
{
backgroundView = UIView(frame: UIScreen.main.bounds)
backgroundView.backgroundColor = .clear
let gesture = UITapGestureRecognizer(target: self, action: #selector(ViewController.handleGesture)) // add this gesture to response my touch
backgroundView.addGestureRecognizer(gesture)
view.addSubview(backgroundView)
}
if optionView == nil
{
optionView = UIView(frame: CGRect(x: -40, y: 30, width: 100, height: 100)) // x = button.wdith / 2 - optionView.width / 2
optionView.backgroundColor = .red
navigationItem.titleView?.addSubview(optionView)
}
}
func handleGesture()
{
if optionView != nil
{
optionView.removeFromSuperview()
optionView = nil
}
if backgroundView != nil
{
backgroundView.removeFromSuperview()
backgroundView = nil
}
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
}