So Im trying to create a UIBarButtonItem with a custom UIView by subclassing it like so.
import UIKit
import SnapKit
class LocationManager: UIBarButtonItem {
let createdView = UIView()
lazy var currentCityLabel: UILabel = {
let currentCityLabel = UILabel()
currentCityLabel.text = "Philadelphia, PA"
guard let customFont = UIFont(name: "NoirPro-SemiBold", size: 20) else {
fatalError("""
Failed to load the "CustomFont-Light" font.
Make sure the font file is included in the project and the font name is spelled correctly.
"""
)
}
currentCityLabel.adjustsFontForContentSizeCategory = true
return currentCityLabel
}()
lazy var downArrow: UIImageView = {
let downArrow = UIImageView()
downArrow.contentMode = .scaleAspectFit
downArrow.image = UIImage(named: "downArrow")
return downArrow
}()
override init() {
super.init()
setupViews()
}
#objc func setupViews(){
customView = createdView
createdView.addSubview(currentCityLabel)
currentCityLabel.snp.makeConstraints { (make) in
make.left.equalTo(createdView.snp.left)
make.top.bottom.equalTo(createdView)
}
createdView.addSubview(downArrow)
downArrow.snp.makeConstraints { (make) in
make.left.equalTo(currentCityLabel.snp.right).offset(5)
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
However, when I create it and assign it in my viewController I see nothing
import UIKit
class ViewController: UICollectionViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
}
#objc func setupViews(){
guard let collection = collectionView else {
return
}
collection.backgroundColor = .white
let customLeftBar = LocationManager()
self.navigationController?.navigationItem.leftBarButtonItem = customLeftBar
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I've looked at other post and none seem to quite match my situation. I'm beginning to think it is because I didn't give the UIView a frame but I am not exactly sure how I would do that in this instance if that is the case. Anyone see anything I don't that could potentially help me solve this problem. Also setting a target doesn't work I tried two different ways and none of them triggers a thing
#objc func setupBarButtonItems(){
let customLeftBar = LocationManager()
customLeftBar.action = #selector(self.leftBarPressed)
customLeftBar.target = self
customLeftBar.customView?.isUserInteractionEnabled = true
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.leftBarPressed))
customLeftBar.customView?.addGestureRecognizer(tapGesture)
navigationItem.leftBarButtonItem = customLeftBar
}
#objc func leftBarPressed(){
print("left bar tapped")
}
Change your adding line from
self.navigationController?.navigationItem.leftBarButtonItem = customLeftBar
to
self.navigationItem.leftBarButtonItem = customLeftBar
When add the barItem, you need to add it via navigationItem of the ViewController, not NavigationController
EDITED for add the action
Your custom UIBarButtonItem is a Custom View's BarButtonItem, so the target and selector will not working.
You can add your custom action by adding a button into your customView, and send the action via closure
Init your closure
var didSelectItem: (() -> Void)?
Add the create button code in your #objc func setupViews()
let button = UIButton(type: .custom)
createdView.addSubview(button)
button.snp.makeConstraints { (maker) in
maker.top.bottom.leading.trailing.equalTo(createdView)
}
// button.backgroundColor = UIColor.cyan // uncomment this line for understand about the barbuttonitem's frame
button.addTarget(self, action: #selector(didTap(_:)), for: .touchUpInside)
and add the function
#objc func didTap(_ button: UIButton) {
print("Did tap button")
}
In your viewController, you can get the tap action by
customLeftBar.didSelectItem = { [weak self] in
self?.leftBarPressed()
}
Unfortunately, your barbuttonitem's default frame is 30x30, so you must be set the frame for your barbuttonitem. If not, you can only catch the tap action in 30x30 area (uncomment the code for see it)
I am trying to create a custom cell in Eureka that display an Image. When tap the image, the image displays full screen with black background.
Usually you do it with view.addSubview(newImageView), but it does not seem like Eureka cell has view class.
Here is what I got so far for the cell:
final class ImageViewCell: Cell<ImageView>, CellType {
#IBOutlet weak var viewImage: UIImageView!
//let storage = Storage.storage().reference()
required init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func setup() {
super.setup()
//cell not selectable
selectionStyle = .none
viewImage.contentMode = .scaleAspectFill
viewImage.clipsToBounds = true
height = {return 300 }
//make userImage reconize tapping
let tapRec = UITapGestureRecognizer(target: self, action: #selector(imageTapHandler(tapGestureRecognizer:)))
viewImage.addGestureRecognizer(tapRec)
viewImage.isUserInteractionEnabled = true
}
#objc func imageTapHandler(tapGestureRecognizer: UITapGestureRecognizer) {
let imageView = tapGestureRecognizer.view as! UIImageView
let newImageView = UIImageView(image: imageView.image)
newImageView.frame = UIScreen.main.bounds
newImageView.backgroundColor = .black
newImageView.contentMode = .scaleAspectFit
newImageView.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(dismissFullscreenImage))
newImageView.addGestureRecognizer(tap)
view.addSubview(newImageView)
}
#objc func dismissFullscreenImage(_ sender: UITapGestureRecognizer) {
sender.view?.removeFromSuperview()
}
override func update() {
super.update()
// we do not want to show the default UITableViewCell's textLabel
textLabel?.text = nil
// get the value from our row
guard let imageData = row.value else { return }
// get user image data from value
let downloadData = imageData.pictureData
viewImage.image = UIImage(data: downloadData)
}
}
I am getting "Use of unresolved identifier 'view'" when trying to addSubview.
I have tried to use contentView instead of view, and the result is like this:
Screenshot for the View
If you want to show the image in fullScreen, cell's contentView or the viewController subView that holds this cell are not the right places to add this image as subview.
A proper solution to this is to use the onCellSelection(something as shown below) callback in your container ViewController and present a new ViewController that display's only image fullscreen or whatever customization you want.
imageCell.onCellSelection({[weak self] (cell, row) in
let imageVC = DisplayViewController()
imageVC.image = cell.viewImage.image
self?.present(imageVC, animated: true, completion: nil)
})
If you want to show fullscreen only when user taps the image in cell then you should get the tapGesture callback in container ViewController and show the image as above.
Thanks for Kamran, I think I found the solution.
for Eureka rows, you can always use row.onCellSelection call back.
in my case I can do this when calling the custom row:
<<< ImageViewCellRow(){ row in
row.value = ImageView(pictureData: data!)
row.onCellSelection(showMe)
}
then create a showMe function like this:
func showMe(cell: ImageViewCell, row: (ImageViewCellRow)) {
print("tapped my ImageViewCell!")
}
now you should be able to present a full screen image as Kamran's code.
I have created a subclass of UILabel, based on an example from here: UILabel doesn't show inputView. I am trying to create an instance of the label inside a class that subclasses UITableViewCell. The issue I am having is that to create an instance of DatePickerLabel it requires an NSCoder.
let dp = DatePickerLabel(coder: NSCoder)
I have this in my class that subclasses UITableViewCell but it doesn't seem to ever be triggered, leading to a null pointer when I run it (I tried using a variable and then assigning it inside this code):
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
print("triggered")
}
Any help is greatly appreciated, DatePickerLabel shown below!
class DatePickerLabel: UILabel {
private let _inputView: UIView? = {
let picker = UIDatePicker()
return picker
}()
private let _inputAccessoryToolbar: UIToolbar = {
let toolBar = UIToolbar()
toolBar.barStyle = UIBarStyle.default
toolBar.isTranslucent = true
toolBar.sizeToFit()
return toolBar
}()
override var inputView: UIView? {
return _inputView
}
override var inputAccessoryView: UIView? {
return _inputAccessoryToolbar
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let doneButton = UIBarButtonItem(title: "Done", style: UIBarButtonItemStyle.plain, target: self, action: #selector(doneClick))
let spaceButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.flexibleSpace, target: nil, action: nil)
_inputAccessoryToolbar.setItems([ spaceButton, doneButton], animated: false)
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(launchPicker))
self.addGestureRecognizer(tapRecognizer)
}
override var canBecomeFirstResponder: Bool {
return true
}
#objc private func launchPicker() {
becomeFirstResponder()
}
#objc private func doneClick() {
resignFirstResponder()
}
}
Cheers!
In the question you linked to, the initialisation function was initWithCoder because that it used to load views from a storyboard or nib. You are not doing that.
So, change your init to a “plain” one, like:
init() {
super.init()
...
}
Many times I would like to implement custom UIViews, but I always have the same problem : my IBOutlets are nil when I want to use them.
I tried everything I could see on the web, but nothing works...
class ImagePickerView: UIView, ImagePickerDelegate {
#IBOutlet weak var imageViewSelected: UIImageView!
//MARK: - Initializers
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let tapGesture = UILongPressGestureRecognizer(target: self, action: #selector(self.onAddPicturePressed))
tapGesture.minimumPressDuration = 0
self.addGestureRecognizer(tapGesture)
}
override func awakeFromNib() {
super.awakeFromNib()
//imageViewSelected = UIImageView()
}
//MARK: - ImagePickerDelegate
func wrapperDidPress(_ imagePicker: ImagePickerController, images: [UIImage]) {}
func doneButtonDidPress(_ imagePicker: ImagePickerController, images: [UIImage]) {
self.imageViewSelected.image = images[0] // (the limit is set to 1 image)
imagePicker.dismiss(animated: true, completion: nil)
}
func cancelButtonDidPress(_ imagePicker: ImagePickerController) {
imagePicker.dismiss(animated: true, completion: nil)
}
//MARK: - Events
func onAddPicturePressed(gesture: UITapGestureRecognizer) {
if gesture.state == .began {
self.backgroundColor = CustomsColors.gray
} else if gesture.state == .ended {
self.backgroundColor = CustomsColors.lightGray
}
showImagePicker()
}
//MARK: - Utils
func showImagePicker() {
let imagePickerController = ImagePickerController()
imagePickerController.delegate = self
imagePickerController.imageLimit = 1
Configuration.mainColor = CustomsColors.green
Configuration.cancelButtonTitle = "Annuler"
Configuration.doneButtonTitle = "OK"
Configuration.noImagesTitle = "Pas d'images disponibles"
self.parentViewController?.present(imagePickerController, animated: true, completion: nil)
}
}
I tried self = Bundle.main.loadNibNamed("ImagePickerView", owner: self, options: nil)?.first as! ImagePickerView but I can't asign self.
I want something really simple, but I don't know how to do it...
Follow following steps in which view controller you want to add nib:
Make Object:
var priceView : FV_ViewToShowPrice!
Give Frame:
priceView = Bundle.main.loadNibNamed("FV_ViewToShowPrice", owner: self, options: nil)![0] as! FV_ViewToShowPrice
priceView.frame = self.view.bounds
Add in view:
self.view.addSubview(priceView)
After initialisation of by subclass of UIImageView I have the following line of code:
self.userInteractionEnabled = true
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "handleTap:"))
I created the necessary associated function :
func handleTap(gestureRecognizer: UITapGestureRecognizer) {
print("In handler")
}
On tapping on the view in question, "In handler was never printed to the console". I then removed the handler function to see if the compiler would complain about the missing function. It didn't.
I'm positively stumped. I'd truly appreciate any light people can shed on this.
Update: My class is actually a UIImageView as opposed to UIView
I was using UITapGestureRecognizer that I placed on a UILabel using Storyboard.
To get this to work I also had to place a checkmark in the block labeled: "User Interaction Enabled" in the UILabel Attributes Inspector in the Storyboard.
I discovered the answer after carefully combing through my code.
One of the parent views was created without supplying a frame:
While it's a noobish enough error to warrant deletion of this questions, odds are someone else will also have the same issue in the future...
Try this
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.view.userInteractionEnabled = true
var tapGesture = UITapGestureRecognizer(target: self, action: Selector("handleTap:"))
self.view.addGestureRecognizer(tapGesture)
}
func handleTap(sender : UIView) {
println("Tap Gesture recognized")
}
In addition to the other answers, this can be caused by adding the gesture recognizer to multiple views. Gesture recognizers are for single views only.
Reference: https://stackoverflow.com/a/5567684/6543020
I ran into this problem with programmatic views.
My UIView with the gesture recognizer had .isUserInteractionEnabled = true, but it did not respond to taps until I set .isUserInteractionEnabled = true for its parent views as well.
Most likely you add UIGestureRecognizer in wrong place. Here is working sample with UIView from storyboard. If you create your UIView dynamically then you should put this initialization in the correct constructor.
class TestView: UIView
{
override func awakeFromNib()
{
self.userInteractionEnabled = true
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "handleTap:"))
}
func handleTap(gestureRecognizer: UITapGestureRecognizer)
{
println("Here")
}
}
I found the solution to this problem after lot of trial and error. So there are two solution two this
1. Either add the GestureRecognizer in viewDidLoad() and turn
userInteractionEnabled = true
2. If using computed property use lazy var instead of let to the property.
lazy var profileImageView: UIImageView = {
let iv = UIImageView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
iv.image = #imageLiteral(resourceName: "gameofthrones_splash")
iv.contentMode = .scaleAspectFill
iv.translatesAutoresizingMaskIntoConstraints = false
iv.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleSelectProfileImageView)))
iv.isUserInteractionEnabled = true
return iv
}()
For anyone that is still having problems even with all the previous answers, make sure you are using UITapGestureRecognizer instead of UIGestureRecognizer, I kept missing this detail while trying to find what was wrong.
This Code works for me with XCode 7.0.1
import UIKit
class ImageView: UIImageView {
init(frame: CGRect, sender: Bool, myImage: UIImage) {
super.init(frame: frame)
self.image = myImage
initBorderStyle(sender)
// enable user interaction on image.
self.userInteractionEnabled = true
let gesture = UITapGestureRecognizer(target: self, action: "previewImage:")
addGestureRecognizer(gesture)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func previewImage(myGesture: UITapGestureRecognizer? = nil) {
print("i'm clicked")
}
private func initBorderStyle(sender: Bool) {
self.layer.masksToBounds = true
self.layer.cornerRadius = 8
self.layer.borderWidth = 0.5
self.layer.borderColor = getBorderColor(sender)
self.backgroundColor = getColor(sender)
}
func getBorderColor(sender: Bool) -> CGColor {
var result: CGColor
if sender {
result = UIColor(red: 0.374, green: 0.78125, blue: 0.0234375, alpha: 0.5).CGColor
} else {
result = UIColor(red: 0.3125, green: 0.6015625, blue: 0.828125, alpha: 0.5).CGColor
}
return result
}
}
Xcode 11.4 Swift 5.2
UserInteraction is enabled by default on Custom UIView
import UIKit
class YourView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapped))
self.addGestureRecognizer(tapGesture)
}
#objc func tapped() {
// do something
}
}
If you want to be notified elsewhere that the custom UIView has been tapped, then it is convenient to use a Notification.
It is best to create this as an extension to prevent it being Stringly typed.
extension Notification.Name {
static let DidTapMyView = Notification.Name("DidTapMyView")
}
In your custom UIView when the tapped function is called
#objc func tapped() {
NotificationCenter.default.post(name: .DidTapMyView, object: self)
}
In your UIViewController where you want to listen for the Notification:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(myViewWasTapped), name: .DidTapMyView, object: nil)
}
#objc func myViewWasTapped() {
// Notified here - do something
}
Here's a list of my findings in 2021, XCode 13:
Make sure both your view and all of its superviews have set width & height constraints(this one is crucial)
Set isUserInteractionEnabled = true for your view
There's no need to set explicit frame or isUserInteractionEnabled for other super views
In addition the above (userInteractionEnabled, clipsToBounds, etc.), if you are working with a view in a child view controller be sure that you have added it as a child with myParentViewController.addChild(myChildViewController) — we've run into a couple of situations now where visible views were not firing recognizing gestures because their view controllers hadn't been added, and presumably the VCs themselves were not being retained.
It turned out that my gesture didn't work because it was in a normal class, and not a subclass of UIView or anything.
I solved the issue by creating a subclass of UIView that I had an instance of in this normal class and placed the gesture logic in that.
I solved my issue by setting the height to the UIView.
optionView.heightAnchor.constraint(equalToConstant: 18).isActive = true
I had the same issue in my programmatically created ViewController. The bug was fixed when I added a backgroundColor to the view.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
let tap = UITapGestureRecognizer(target: self.view, action: #selector(UIView.endEditing))
view.addGestureRecognizer(tap)
}
}