UIDatePicker with toolbar but without text field - ios

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?) {
}
}

Related

Why is the toolbar not showing when I tap on the textfield?

I would like to implement a done toolbar above the numpad keyboard when the textfield is tapped however the toolbar is not showing up for some reason.
The following code sample has been used:
extension UITextField{
#IBInspectable var doneAccessory: Bool{
get{
return self.doneAccessory
}
set (hasDone) {
if hasDone{
addDoneButtonOnKeyboard()
}
}
}
func addDoneButtonOnKeyboard()
{
let doneToolbar: UIToolbar = UIToolbar(frame: CGRect.init(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 50))
doneToolbar.barStyle = .default
let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let done: UIBarButtonItem = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(self.doneButtonAction))
let items = [flexSpace, done]
doneToolbar.items = items
doneToolbar.sizeToFit()
self.inputAccessoryView = doneToolbar
}
#objc func doneButtonAction()
{
self.resignFirstResponder()
}
}
Make sure in your storyboard, TextField's property inspector doneAccessory property is set to ON
and O/P looks like

In Swift code my Label is not showing up in my ViewController

When the ViewController is displayed on the phone the Label is not displayed
I am new to iOS and swift, I have search YouTube extensively to no avail. In the code you can see I have made two attempts to add two labels. Not that none are showing. The navigation elements are working correctly. The background is being set correctly also.
import UIKit
class WelcomeController: UIViewController {
// MARK: - Properties
var pageTitle: UILabel!
// MARK: - Init
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
let frameLabel:CGRect = CGRect(x: 20, y: 0, width: UIScreen.main.bounds.width - 20, height: 50)
let label:UILabel = UILabel(frame: frameLabel)
label.text = "This text is not showing up on the screen!!! his text is not showing up on the screen!!! his text is not showing up on the screen!!!"
label.textColor = .black
label.textAlignment = .center
view.addSubview(label)
configureUI()
configurePageTitleLabel()
}
// MARK: - Selectors
#objc func handleDismiss() {
dismiss(animated: true, completion: nil)
}
// MARK: - Helper Functions
func configureUI() {
navigationController?.navigationBar.barTintColor = .blue
navigationItem.title = "Welcome"
let textAttributes = [NSAttributedString.Key.foregroundColor:UIColor.white]
navigationController?.navigationBar.titleTextAttributes = textAttributes
navigationItem.leftBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "ic_menu_white_3x").withRenderingMode(.alwaysOriginal), style: .plain, target: self, action: #selector(handleDismiss))
}
// MARK - Page Contents Functions
func configurePageTitleLabel() {
pageTitle = UILabel()
pageTitle.text = "Welcome"
pageTitle.textAlignment = .center
pageTitle.textColor = .black
let frameTitle:CGRect = CGRect(x: 20, y: 20, width: UIScreen.main.bounds.width - 20, height: 50)
pageTitle.drawText(in: frameTitle)
view.addSubview(pageTitle)
view.backgroundColor = .gray
}
}
The y value should be more than 64 ie. 20 status bar + 44 Navigation bar.
Also, you don't need drawText and frame tile.
Simply adding
pageTitle.frame = CGRect(x: 20, y: 200, width: UIScreen.main.bounds.width - 20, height: 50)
would work.

UINavigationItem not displaying left and right barButtons

I'm testing my app on an iPhone 6+. I have a navigation bar which includes two labels, a left bar button (not shown initially), and a right bar button. The problem is that the labels are shown but no matter how hard I try I can't make it show the buttons. They work(if you tap on where they should be, they work as expected) but are not shown. The tests on an iPhone 5s (physic) and iPhone X (simulator) went correctly and the buttons are shown.
Is there any problem with my code?
Thank you.
override func viewDidLoad() {
super.viewDidLoad()
webView.delegate = self
loadWeb()
let button = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.action, target: self, action: #selector(share))
button.tintColor = UIColor.white
self.navigationItem.rightBarButtonItem = button
let frameLabelURLTitle = CGRect.init(x: 25, y: 2, width: (self.navigationController?.navigationBar.frame.size.width)! - 182, height: (self.navigationController?.navigationBar.frame.size.height)! - 20)
let frameLabelURL = CGRect.init(x: 25, y: 2 + frameLabelURLTitle.height, width: (self.navigationController?.navigationBar.frame.size.width)! - 182, height: 10)
let viewLabel = UIView.init(frame: frameLabelURLTitle)
labelTitleURL = UILabel.init(frame: frameLabelURLTitle)
labelURL = UILabel.init(frame: frameLabelURL)
labelURL.textColor = UIColor.white
labelTitleURL.textColor = UIColor.white
labelURL.font = UIFont.systemFont(ofSize: 13.0)
viewLabel.addSubview(labelTitleURL)
viewLabel.addSubview(labelURL)
self.navigationController?.navigationBar.addSubview(viewLabel)
loadingLabel.text = "loadingWeb".localized()
loadingLabel.sizeToFit()
self.navigationController?.isNavigationBarHidden = true
self.placeHolderView.layer.insertSublayer(initGradient(bounds: self.view.bounds, isHorizontal: false), at: 0)
self.loadingGifImageView.image = UIImage.gif(asset: "01-GIF_LOGO")
}
func webViewDidFinishLoad(_ webView: UIWebView) {
self.placeHolderView.isHidden = true
self.navigationController?.isNavigationBarHidden = false
labelTitleURL.text = webView.stringByEvaluatingJavaScript(from: "document.title")
labelURL.text = webView.request?.url?.absoluteString.components(separatedBy: "/")[2]
self.navigationItem.leftBarButtonItem = nil
if !(webView.request?.url?.absoluteString.contains("/blog/"))! {
let newBackButton = UIBarButtonItem(image: UIImage(named: "bt_close")?.withRenderingMode(.alwaysOriginal), style: UIBarButtonItemStyle.plain, target: self, action: #selector(back))
newBackButton.isEnabled = true
print(newBackButton.style)
self.navigationItem.setLeftBarButton(newBackButton, animated: true)
self.navigationItem.leftBarButtonItem = newBackButton
self.navigationItem.backBarButtonItem = newBackButton
}
}

Swift: How do I create a custom UINavigationBar and add a custom back button?

I am trying to create a custom navigationBar.
I am hiding the original navigationBar in viewWillAppear like so:
override func viewWillAppear(_ animated: Bool) {
self.navigationController?.isNavigationBarHidden = true
}
I am subclassing UINavigationBar like so:
let navBar: UINavigationBar = {
let view = UINavigationBar()
view.backgroundColor = .clear
view.isTranslucent = true
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
In viewDidLoad I am calling setupNavBar():
func setupNavBar() {
view.addSubview(navBar)
self.navBar.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 80)
let backButton = UIBarButtonItem(image: UIImage(named:"backThick"), style: .plain, target: self, action: #selector(popControllerOffStack))
}
The problem is that backButton is added to the original navigationBar that is being hidden. This makes me think I am incorrectly creating the navigationBar. How do I add the button to navBar?
Updated Code (still not working):
class CustomNavBar: UINavigationBar {
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = .red
}
}
// In the viewController
let navBar = CustomNavBar()
override func viewDidLoad() {
super.viewDidLoad()
setupNavBar()
}
func setupNavBar() {
view.addSubview(navBar)
navBar.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 80)
let backButton = UIBarButtonItem(image: UIImage(named:"backThick"), style: .plain, target: self, action: #selector(popControllerOffStack))
self.navigationItem.leftBarButtonItem = backButton
}
The button is being added to the original navigationBar.
You are not subclassing UINavigationBar. Rather, you are creating a new instance of UINavigationBar and modifying its properties inside a computed variable. This means each time you access navBar, you are initializing a new UINavigationBar object.
To create a subclass:
class MyCustomNavigationBar: UINavigationBar {
// Set properties in here after initialization
}
Once you have a proper subclass created, You can initialize an instance like so:
var navBar = MyCustomNavigationBar()
Finally, add your button to the navigation bar:
let backButton = UIBarButtonItem(image: UIImage(named:"backThick"), style: .plain, target: self, action: #selector(popControllerOffStack))
// Assuming 'self' is an instance of UINavigationController()
self.navigationItem.leftBarButtonItem = backButton
See the official Swift Programming Language Guide on Inheritance.
Swift 3.0
You can set custom Back button as like below
self.navigationItem.hidesBackButton = true
let backButton = UIBarButtonItem(image: UIImage(named: "image_name"), style: .plain, target: self, action: #selector(Class.methodName))
backButton.tintColor = UIColor.white
self.navigationItem.leftBarButtonItem = backButton
Also you can try below code:
let btnLeftMenu: UIButton = UIButton()
btnLeftMenu.setImage(UIImage(named: "image_name"), for:UIControlState())
btnLeftMenu.addTarget(self, action: #selector(moveImage), for:UIControlEvents.touchUpInside)
btnLeftMenu.frame = CGRect(x: 0, y: 0, width: 25, height: 25)
let barButton = UIBarButtonItem(customView: btnLeftMenu)
self.navigationItem.leftBarButtonItem = barButton*

How can I get events to my (sub) UIView?

I'm new to Swift and have a hard time understand the event flow. The code below can be run directly in an xcode playground. I have a white UIView in the background. This view has a brown button and a red view as sub-views. Click on them and the events are logged in the controller, just as expected.
But the controller of this white view also adds another view, that has it's own controller class (SubviewController). SubviewController is green and has a blue subview with a black button. Question is... why don't I get any logs from the green, blue and black views/buttons?
import Foundation
import UIKit
import PlaygroundSupport
class TestViewController : UIViewController {
let playButton: UIButton = {
let playButton = UIButton(frame: CGRect(x: 155, y: 135, width: 160, height: 40))
playButton.setTitle("BROWN BUTTON", for: .normal)
playButton.backgroundColor = UIColor.brown
return playButton
}()
override func loadView() {
let viewWhite = UIView()
viewWhite.backgroundColor = UIColor.white
let viewRed = UIView()
viewRed.backgroundColor = UIColor.red
viewRed.frame = CGRect(x: 20, y: 20, width: 40, height: 10)
viewRed.clipsToBounds = true
let recognizer2 = UITapGestureRecognizer(target: self, action: #selector (self.handleTapRed(_:)))
viewRed.addGestureRecognizer(recognizer2)
let recognizer = UITapGestureRecognizer(target: self, action: #selector (self.handleTap(_:)))
viewWhite.addGestureRecognizer(recognizer)
playButton.addTarget(self, action: #selector (self.action) , for: .touchUpInside)
let catList = SubviewController()
viewWhite.addSubview(catList.view)
viewWhite.addSubview(playButton)
viewWhite.addSubview(viewRed)
self.view = viewWhite
}
func action() {
print("Brown button tapped")
}
func handleTap(_ sender:UITapGestureRecognizer){
print("WHITE VIEW (background view) TAPPED")
}
func handleTapRed(_ sender:UITapGestureRecognizer){
print("RED VIEW TAPPED")
}
}
class SubviewController: UIViewController {
let buttonBlack: UIButton = {
let button = UIButton(frame: CGRect(x: 40, y: 10, width: 170, height: 20))
button.backgroundColor = UIColor.black
button.setTitle("BLACK BUTTON", for: .normal)
return button
}()
let viewBlue: UIView = {
let v = UIView()
v.backgroundColor = UIColor.blue
v.frame = CGRect(x: 20, y: 40, width: 240, height: 60)
v.clipsToBounds = true
return v
}()
override func loadView() {
super.loadView()
self.view.backgroundColor = UIColor.green
buttonBlack.addTarget(self, action: #selector (self.blackKlick) , for: .touchUpInside)
self.view.addSubview(viewBlue)
self.view.frame = CGRect(x: 0, y: 40, width: 240, height: 60)
self.view.clipsToBounds = true
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector (self.handleTapGreen(_:))))
viewBlue.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector (self.handleTapBlue(_:))))
viewBlue.addSubview(buttonBlack)
}
func blackKlick() {
print("Black button tapped")
}
func handleTapBlue(_ sender:UITapGestureRecognizer){
print("BLUE VIEW TAPPED")
}
func handleTapGreen(_ sender:UITapGestureRecognizer){
print("GREEN VIEW TAPPED")
}
}
PlaygroundPage.current.liveView = TestViewController()
Thanks for any help!
This line in your current code:
let catList = SubviewController()
creates a local instance of SubvieController. As soon as you exit the loadView() func, that instance is gone.
So, you need a class-level variable to keep that instance around. Add this line:
class TestViewController : UIViewController {
var catList: SubviewController!
and then remove the let from the instantiation line in loadView():
catList = SubviewController()

Resources