Is there a way to make a completely transparent UIButton that works? - swift

I've a custom UINavigationItem title view. It has a label pushed to the top of it, which I've enabled to respond to .touchDown and send an action.
However, the taps don't register near the top of the label, presumably because the active region is clipped. So I configured another invisible view (not in navigation item), and set it up as a control, and positioned it above that navigation title view label.
However, it doesn't work unless I set the 'invisible' view's alpha to at least 0.02, because Apple seems to intentionally disable action for a view with an alpha less than that. Unfortunately, against a black screen in dark mode, the 'invisible' hitpad view shows up as a slightly grey rectangle, which is not a good aesthetic.
I guess I could go to some lengths to try to make the button background color match the screen background at that location, but it seems a bit tacky.
What alternatives might I have?

You can simply create a blank png image, and add it in top of your title view. make sure to set the imageView and the title view isUserInteractionEnabled properties to true:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .green
let imageView = UIImageView()
imageView.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(
target: self,
action: #selector(tap)
)
imageView.addGestureRecognizer(tap)
imageView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
imageView.image = UIImage(named: "blank")
let titleLabel = UILabel()
titleLabel.text = "Transparent Button"
titleLabel.autoresizingMask = [.flexibleHeight, .flexibleWidth]
titleLabel.addSubview(imageView)
navigationItem.titleView = titleLabel
navigationItem.titleView?.isUserInteractionEnabled = true
}
#objc func tap(_ gesture: UITapGestureRecognizer) {
print(#function)
}
}
Sample project
You can also just add your gesture recognizer directly to your titleView. No need for the transparent image at all unless you need to control the area of the gesture:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .green
let tap = UITapGestureRecognizer(
target: self,
action: #selector(tap)
)
let titleLabel = UILabel()
titleLabel.text = "Transparent Button"
titleLabel.autoresizingMask = [.flexibleHeight, .flexibleWidth]
titleLabel.addGestureRecognizer(tap)
navigationItem.titleView = titleLabel
navigationItem.titleView?.isUserInteractionEnabled = true
}
#objc func tap(_ gesture: UITapGestureRecognizer) {
print(#function)
}
}

This is an adaptation of the #LeoDabus' Accepted answer that works. However it was utterly informed by his explanation and example. The only meaningful change I made to Leo's example was to create a real empty image programmatically, and drop the label generation. Without a real empty UIImage(), the only way to make taps on the region work that I found is to set the image view's background color to non-clear.
func emptyImage(with size: CGSize) -> UIImage?
{
UIGraphicsBeginImageContext(size)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
func configureButtons() {
let imageView = UIImageView(image: emptyImage(with: CGSize(width: view.frame.size.width - 250, height: 44)))
imageView.frame = CGRect(x: 140, y: self.view.safeAreaInsets.top + 50,
width: view.frame.size.width - 250, height: 44)
imageView.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(actionEnableTitleEditing))
imageView.addGestureRecognizer(tap)
imageView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
view.addSubview(imageView)
}

Related

Swift: UIBarButtonItem with bigger size than UIToolbar

I have a UIToolbar installed on my Viewcontroller on the bottom via Storyboard. I also added a bottom in the Storyboard and now I want to give this bottom a greater height than the toolbar itself.
It should be something like that, but it cannot be a Tabbar but needs to be a Toolbar, as the items on it are purely contextual actions and not top level navigation items (see Apple guidelines here and here):
I tried the following code in my Viewcontroller without success (as mentioned here):
class MyVC: UIViewController {
#IBOutlet var ibOutletForButton: UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
let menuBtn = UIButton(type: .custom)
menuBtn.frame = CGRect(x: 0.0, y: 0.0, width: 20, height: 120)
menuBtn.setImage(UIImage(named:"iconImage"), for: .normal)
menuBtn.addTarget(self, action: #selector(onMenuButtonPressed(_:)), for: UIControlEvents.touchUpInside)
let menuBarItem = UIBarButtonItem(customView: menuBtn)
let currWidth = menuBarItem.customView?.widthAnchor.constraint(equalToConstant: 24)
currWidth?.isActive = true
let currHeight = menuBarItem.customView?.heightAnchor.constraint(equalToConstant: 124)
currHeight?.isActive = true
ibOutletForButton = menuBarItem
}
}
How could I get the button bigger and moved up that it looks like on the image?
One way you could do this is to add the button directly to the UIViewController instead of to the UIToolbar. You have then complete freedom of positioning and sizing.
As you don't use a UITabBar, you will stay within your UIViewController and it should be no problem
You can create 4 BarbuttonItem after first 2, give some flexible space between items and add your 'plus' button to toolbar directly in that space.
#IBOutlet weak var myToolBar: UIToolbar!
let menuBtn = UIButton(type: .custom)
override func viewDidLoad() {
super.viewDidLoad()
let menuBtn = UIButton(type: .custom)
menuBtn.frame = CGRect(x: myToolBar.center.x-10, y: -60, width: 20, height: 120)
menuBtn.setImage(UIImage(named:"iconImage"), for: .normal)
menuBtn.addTarget(self, action: #selector(onMenuButtonPressed(_:)), for: UIControlEvents.touchUpInside)
let spacer = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let items = myToolBar.items!
myToolBar.setItems([items[0],items[1],spacer,items[2],items[3]], animated: false)
myToolBar.addSubview(menuBtn)
}

Button action does not react when .touchUpInside inside a navigation bar

I created a UICollectionViewController to simulate a Feed app and I wanted to configure the top navigation bar similar to the Twitter one, with custom buttons that trigger actions, here's mine
The problem I'm facing is that when I click on the black profile icon, the action is not triggered.
Here's my code:
import UIKit
class HomeViewController: UICollectionViewController {
//MARK: - Properties
private lazy var profileButton: UIButton = {
let button = UIButton(type: .system)
button.setImage(UIImage(systemName: "person.fill"), for: .normal)
button.tintColor = UIColor(rgb: 0x79CBBF)
button.addTarget(self, action: #selector(didTapProfile), for: .touchUpInside)
return button
}()
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setupNavBar()
}
// MARK: - Helpers
func setupNavBar() {
let width = view.frame.width
let titleView = UIView()
titleView.backgroundColor = .clear
titleView.frame = .init(x: 0, y: 0, width: width, height: 50)
titleView.addSubview(profileButton)
profileButton.tintColor = .black
profileButton.centerY(inView: titleView, leftAnchor: titleView.leftAnchor, paddingLeft: 0)
titleView.addSubview(filterButton)
filterButton.centerY(inView: titleView, leftAnchor: profileButton.rightAnchor, paddingLeft: view.frame.width - 60 - 16)
navigationItem.titleView = titleView
}
// MARK: - Actions
#objc func didTapProfile() {
print("did tap profile")
}
As said below, I added a .addTarget to the button but the #selector(didTapProfile) function does not get triggered when the button is inside the navigation bar.
Any hints on how to do this?
Instead of creating a title view and measuring its size and stuff... there are a load of convenience functions for doing this...
Take a look here... https://www.hackingwithswift.com/example-code/uikit/how-to-add-a-bar-button-to-a-navigation-bar
You can do something like...
navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(didTapProfile))
There are different ways to create UIBarButtonItem with images and text also...
https://developer.apple.com/documentation/uikit/uibarbuttonitem

How can I center a button on view programmatically

I am trying to center a Button onto the bottom of a view but it never appears. The only time it appears is when I uncomment takePhotoButton.frame. What is the proper way to do this?
import UIKit
import AVFoundation
class InputViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let photoPreviewImageView = UIImageView()
photoPreviewImageView.frame = view.bounds
photoPreviewImageView.backgroundColor = UIColor.green
view.addSubview(photoPreviewImageView)
let imageOfPhotoButton = UIImage(named: "smallcircle.circle.fill") as UIImage?
let takePhotoButton = UIButton(type: .custom) as UIButton
takePhotoButton.setImage(imageOfPhotoButton, for: .normal)
//takePhotoButton.frame = CGRect(x: 10, y: 10, width: 60, height: 60) // It will appear with this code however i took it away because im trying to center it at the bottom of the screen
takePhotoButton.center = view.center
photoPreviewImageView.addSubview(takePhotoButton)
}
}
Use constraint anchors. After you add the takePhotoButton set them the following way:
takePhotoButton.bottomAnchor.constraint(equalTo: photoPreviewImageView.bottomAnchor).isActive = true
takePhotoButton.centerXAnchor.constraint(equalTo: photoPreviewImageView.centerXAnchor).isActive = true
This will set make your button have the same bottom and center as it's container.
Good day,
you have to add constraint.
import UIKit
class ViewController: UIViewController {
var loginButton : UIButton = {
let button = UIButton(type: .system)
button.setTitle("Login", for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = .red
button.tintColor = .white
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
constraintsInit()
}
func constraintsInit(){
view.addSubview(loginButton)
NSLayoutConstraint.activate([
loginButton.centerYAnchor.constraint(equalTo: self.view.centerYAnchor),
loginButton.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
loginButton.heightAnchor.constraint(equalToConstant: 30),
loginButton.leadingAnchor.constraint(equalTo: self.view.leadingAnchor,constant: 30),
loginButton.trailingAnchor.constraint(equalTo: self.view.trailingAnchor,constant: -30),
])
}
}
on youtube you can find several people that explain how create the views, using only code.

How filter and forward gestures on UIView who covers another UIView with the same size and position?

Description is simple. I have 2 subviews (topView and bottomView UIViews) into main view and both cover whole screen. bottomView is bellow topView.
For topView I added double-tap gesture using UITapGestureRecognizer and for bottomView I added pinch gesture using UIPinchGestureRecognizer.
The goal is: When I made pinch gesture (on the topView) I want to forward that pinch gesture to the bottomView. Double-tap gesture should be executed on the topView. In other words, how to forward only pinch gesture to the bottomView and every other gesture should be executed by topView.
Note: Keep in mind that both UIViews cover whole screen.
I provided code for the start.
I tried to solve this problem overriding UIView's methods hitTest and point, but no luck.
import UIKit
let width = UIScreen.main.bounds.width
let height = UIScreen.main.bounds.height
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let bottomView: UIView = {
let view = UIView()
view.frame = CGRect(x: 0, y: 0, width: width, height: height)
view.backgroundColor = .yellow
return view
}()
let topView: UIView = {
let view = UIView()
view.frame = CGRect(x: 0, y: 0, width: width, height: height)
view.backgroundColor = .orange
return view
}()
let doubleTouch = UITapGestureRecognizer(target: self, action: #selector(doubleTap(sender:)))
doubleTouch.numberOfTapsRequired = 2
topView.addGestureRecognizer(doubleTouch)
let pinch = UIPinchGestureRecognizer(target: self, action: #selector(pinch(sender:)))
bottomView.addGestureRecognizer(pinch)
view.addSubview(bottomView)
view.addSubview(topView)
}
#objc func doubleTap(sender: UITapGestureRecognizer) {
print("Double Tap")
}
#objc func pinch(sender: UIPinchGestureRecognizer) {
print("Pinch")
}
}

iOS - add image and text in title of Navigation bar

I would like to create a nav bar similar to what's in the image that's attached.
The title of the nav bar will be a combination of an image and text.
Should this be done per any best practice?
How can it be done?
As this answer shows, the easiest solution is to add the text to your image and add that image to the navigation bar like so:
var image = UIImage(named: "logo.png")
self.navigationItem.titleView = UIImageView(image: image)
But if you have to add text and an image separately (for example, in the case of localization), you can set your navigation bar's title view to contain both image and text by adding them to a UIView and setting the navigationItem's title view to that UIView, for example (assuming the navigation bar is part of a navigation controller):
// Only execute the code if there's a navigation controller
if self.navigationController == nil {
return
}
// Create a navView to add to the navigation bar
let navView = UIView()
// Create the label
let label = UILabel()
label.text = "Text"
label.sizeToFit()
label.center = navView.center
label.textAlignment = NSTextAlignment.Center
// Create the image view
let image = UIImageView()
image.image = UIImage(named: "Image.png")
// To maintain the image's aspect ratio:
let imageAspect = image.image!.size.width/image.image!.size.height
// Setting the image frame so that it's immediately before the text:
image.frame = CGRect(x: label.frame.origin.x-label.frame.size.height*imageAspect, y: label.frame.origin.y, width: label.frame.size.height*imageAspect, height: label.frame.size.height)
image.contentMode = UIViewContentMode.ScaleAspectFit
// Add both the label and image view to the navView
navView.addSubview(label)
navView.addSubview(image)
// Set the navigation bar's navigation item's titleView to the navView
self.navigationItem.titleView = navView
// Set the navView's frame to fit within the titleView
navView.sizeToFit()
Use horizontal UIStackView should be much cleaner and easier
Please add the next extension to UIViewController
extension UIViewController {
func setTitle(_ title: String, andImage image: UIImage) {
let titleLbl = UILabel()
titleLbl.text = title
titleLbl.textColor = UIColor.white
titleLbl.font = UIFont.systemFont(ofSize: 20.0, weight: .bold)
let imageView = UIImageView(image: image)
let titleView = UIStackView(arrangedSubviews: [imageView, titleLbl])
titleView.axis = .horizontal
titleView.spacing = 10.0
navigationItem.titleView = titleView
}
}
then use it inside your viewController:
setTitle("yourTitle", andImage: UIImage(named: "yourImage"))
(this will align the text and the icon together to the center, if you want the text to be centered and the icon in the left, just add an empty UIView with width constraint equal to the icon width)
here is my 2 cents for Swift 4, since accepted answer didn't work for me (was mostly off the screen):
// .. in ViewController
var navBar = CustomTitleView()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// =================== navBar =====================
navBar.loadWith(title: "Budget Overview", leftImage: Images.pie_chart)
self.navigationItem.titleView = navBar
}
class CustomTitleView: UIView
{
var title_label = CustomLabel()
var left_imageView = UIImageView()
override init(frame: CGRect){
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder){
super.init(coder: aDecoder)
setup()
}
func setup(){
self.addSubview(title_label)
self.addSubview(left_imageView)
}
func loadWith(title: String, leftImage: UIImage?)
{
//self.backgroundColor = .yellow
// =================== title_label ==================
//title_label.backgroundColor = .blue
title_label.text = title
title_label.font = UIFont.systemFont(ofSize: FontManager.fontSize + 5)
// =================== imageView ===================
left_imageView.image = leftImage
setupFrames()
}
func setupFrames()
{
let height: CGFloat = Navigation.topViewController()?.navigationController?.navigationBar.frame.height ?? 44
let image_size: CGFloat = height * 0.8
left_imageView.frame = CGRect(x: 0,
y: (height - image_size) / 2,
width: (left_imageView.image == nil) ? 0 : image_size,
height: image_size)
let titleWidth: CGFloat = title_label.intrinsicContentSize.width + 10
title_label.frame = CGRect(x: left_imageView.frame.maxX + 5,
y: 0,
width: titleWidth,
height: height)
contentWidth = Int(left_imageView.frame.width)
self.frame = CGRect(x: 0, y: 0, width: CGFloat(contentWidth), height: height)
}
var contentWidth: Int = 0 //if its CGFloat, it infinitely calls layoutSubviews(), changing franction of a width
override func layoutSubviews() {
super.layoutSubviews()
self.frame.size.width = CGFloat(contentWidth)
}
}
Swift 4.2 + Interface Builder Solution
As a follow-on to Lyndsey Scott's answer, you can also create a UIView .xib in Interface Builder, use that to lay out your title and image, and then update it on-the-fly via an #IBOutlet. This is useful for dynamic content, internationalization, maintainability etc.
Create a UIView subclass with a UILabel outlet and assign your new .xib to this class:
import UIKit
class FolderTitleView: UIView {
#IBOutlet weak var title : UILabel!
/// Create an instance of the class from its .xib
class func instanceFromNib() -> FolderTitleView {
return UINib(nibName: "FolderTitleView", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! FolderTitleView
}
}
Connect the label to your outlet (title in my example) in your .xib, then in your UIViewController:
/// Reference to the title view
var folderTitleView : FolderTitleView?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Set the screen title to match the active folder
updateTitle()
}
/// Updates the title of the navigation controller.
func updateTitle() {
self.title = ""
if folderTitleView == nil {
folderTitleView = FolderTitleView.instanceFromNib()
self.navigationItem.titleView = folderTitleView
}
folderTitleView!.title.text = "Listening"
folderTitleView!.layoutIfNeeded()
}
This results in a nice self-centering title bar with an embedded image that you can easily update from code.
// worked for me
create a view and set the frame
now add the image in the view and set the frame
after adding the image, add the label in same view and set the frame
after adding the image and label to view, add same view to navigationItem
let navigationView = UIView(frame: CGRect(x: 0, y: 0, width: 50 , height: 55))
let labell : UILabel = UILabel(frame: CGRect(x: -38, y: 25, width: 150, height: 25))
labell.text = "Your text"
labell.textColor = UIColor.black
labell.font = UIFont.boldSystemFont(ofSize: 10)
navigationView.addSubview(labell)
let image : UIImage = UIImage(named: ValidationMessage.headerLogoName)!
let imageView = UIImageView(frame: CGRect(x: -20, y: 0, width: 100, height: 30))
imageView.contentMode = .scaleAspectFit
imageView.image = image
//navigationItem.titleView = imageView
navigationView.addSubview(imageView)
navigationItem.titleView = navigationView