Swift: Set UIView as the background of UIBarButton/UIButton - ios

How can I set a UIView as the background of a UIBarButton? UIBarButton/UIButton should contain its title and background image.
I tried with overriding UIButton class, but it doesn't work. If someone has a solution please advise me.

// For UIBarButtonItem (just set customView parameter)
let frame = CGRect(x: 0, y: 0, width: 100, height: 40)
let customView = UIView(frame: frame)
customView.backgroundColor = .red
let barButtonItem = UIBarButtonItem(customView: customView)
// for UIButton
// 1. Subview case (add subview on your target UIButton, send to back so that it does not cover title)
let button0 = UIButton(frame: frame)
button0.addSubview(customView)
button0.sendSubviewToBack(customView)
button0.setTitle("Button", for: UIControl.State())
// 2. Rendered image case (as an alternative render your view to an image and add as a background image)
// Extension for capturing a UIVIew as an image:
extension UIImage {
convenience init?(view: UIView) {
UIGraphicsBeginImageContext(view.frame.size)
guard let ctx = UIGraphicsGetCurrentContext() else {
return nil
}
view.layer.render(in: ctx)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
guard let cgImage = image?.cgImage else {
return nil
}
self.init(cgImage: cgImage)
}
}
let button1 = UIButton(frame: frame)
let customViewImage = UIImage.init(view: customView)
button1.setBackgroundImage(customViewImage, for: UIControl.State())
button1.setTitle("Button", for: UIControl.State())

You can add view as subView of button:
extension UIButton {
func setBackgroundView(view: UIView) {
view.frame = CGRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height)
view.isUserInteractionEnabled = false
self.addSubview(view)
}
}

You can use CustomButton which inherits from UIControl. By using xib you canmanage your UI as per your requirements. You can add n number of UI elements in it. You will get default event handler with this like UIButton (like touchupinside, touchupoutside, valuechange). You can provide IBActions for same. For reference attaching some screen shots. Let me know if you face any while implementing this. Keep coding.

Maybe you should use UIImageView.image instead of the UIView itself to set the bottom background image.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var myButton: UIButton!
#IBOutlet weak var myView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
myButton.setBackgroundImage(myView.image, for: .normal)
}
}

Related

Change UIImage on UIButton with separate Swift file if loop

Goal of the code:
To change a UIButton's UIImage that was already set on viewDidLoad() using an outside function that is not in viewDidLoad(), that is in an entirely separate swift file in the project.
I am however able to successfully change the UIImage of the UIButton by linking the button to a function that does the standard exampleButton.setImage(exampleButtonimage, for: .normal).
Code below:
class ViewController: UIViewController {
static let vcShared = ViewController()
var exampleButton = UIButton(frame: CGRect(x: 146, y: 140, width: 100, height: 50))
let exampleButtonimage = UIImage(named: "ExampleImage")
...
override func viewDidLoad() {
super.viewDidLoad()
exampleButton.setImage(exampleButtonimage , for: .normal)
}
}
Attempting to set UIButton's UIImage in the Second file:
let otherButtonimage = UIImage(named: "OtherImage")
if someVariable > 55000 {
ViewController.vcShared.exampleButton.setImage(otherButtonimage , for: .normal)
}
When the if criteria is met, the UIButton in viewDidLoad() does not change its image (I know the if criteria is met as I have a separate print("Hello World") in the if loop that consistently works.) However, nothing happens with the UIButton when the criteria is met.
Best practice is cosntruct delegate or observer pattern for that situation but the code below is going to work your code.
class ViewController: UIViewController {
static var exampleButton = UIButton(frame: CGRect(x: 146, y: 140, width: 100, height: 50))
let exampleButtonimage = UIImage(named: "ExampleImage")
...
override func viewDidLoad() {
super.viewDidLoad()
exampleButton.setImage(exampleButtonimage , for: .normal)}}
And where change code;
let otherButtonimage = UIImage(named: "OtherImage")
if someVariable > 55000 {
ViewController.exampleButton.setImage(otherButtonimage , for: .normal)}

Create UIButton on top layer UITableViewController like as AirBnB

How to create UIButton in UITableViewController like as AirBnB map.
Highlighted in red in screenshot.
It's quite easy actually, just create the table view as you always do (either storyboard or code) then create the button, position it correctly, set constraints and make sure it's not a subview of the table view but the view that contains the table view.
Since you didn't specify whether you use the storyboard or not, I did use it for the example below.
See this extremely simple demo I've just created:
This is the view hierarchy:
And these are the constraints for the button:
Create a button first and then then override the function scrollViewDidScroll():
import UIKit
class YourTableViewController: UITableViewController {
private let button = UIButton(type: UIButton.ButtonType.custom) as UIButton
override func viewDidLoad() {
let image = UIImage(named: "Image.png")
button.frame = CGRect(x: yourXpos, y: yourYPos, width: 60, height: 60)
button.setImage(image, for: .normal)
button.clipsToBounds = true
button.layer.cornerRadius = 30
button.addTarget(self, action: #selector(buttonClicked(_:)), for:.touchUpInside)
areaOfTableView.addSubview(button)
}
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offset = self.areaOfTableView.contentOffset.y
button.frame = CGRect(x: yourXpos, y: offset + yourYpos, width: btn.frame.size.width, height: btn.frame.size.height)
}
#objc private func buttonClicked(_ button: UIButton) {
// Action when you tapped the button
}
}

Swift: Cannot click button in subview

I have a ViewController, LockScreenVC, and am adding a UIView to it.
In that UIView, I am adding a UIButton. The button shows up, but I cannot click the button. If I add the button instead to the LockScreenVC, I can click the button - but it is no longer part of the UIView.
I've tried setting isUserInteractionEnabled to true, but that has not done anything.
import UIKit
class LockScreenVC: UIViewController {
lazy var detailContainer : UIView = {
let v = UIView();
v.backgroundColor = UIColor.black.withAlphaComponent(0.2)
return v
}()
lazy var detailButton: UIButton = {
let btn = UIButton(frame: CGRect(x: 10, y: 10, width: 300, height: 150))
btn.setTitle("Click Me", for: .normal)
return btn
}()
override func viewDidLoad() {
super.viewDidLoad()
detailButton.addTarget(self, action: #selector(onDetailButtonPressed), for: .touchUpInside)
detailContainer.addSubview(detailButton)
detailContainer.isUserInteractionEnabled = true
view.addSubview(detailContainer)
}
#objc func onDetailButtonPressed() {
print("You pressed the button!")
}
}
Your detailContainer has zero frame. Please set frame for it

Is this a good way to show a view by touch a button at the middle of the navigation bar and remove it by touch anywhere else?

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()
}

Swift Custom Navigation Controller Class

Everyone who reads it, hello and thank you. I am writing an application like Viber or WhatsApp for iOS devices using Xcode 8.2.1 and Swift 3.0. I have a problem: I want my app has a navigation controller but not a simple one, I want it to be a custom one. There is a print screen from WhatsApp to be understanding, what I am trying to achieve:
As we can see, this navigation controller has 3 buttons, label and image with avatar. I also want to change my color to green one. Due to do this I have created class:
import UIKit
class CustomNavigationController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
self.navigationBar.barTintColor = UIColor.green
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 39, height: 39))
imageView.contentMode = .scaleAspectFit
let image = UIImage(named: "logo")
imageView.image = image
navigationItem.titleView = imageView
}
}
And it gives this result:
As you can see it is without picture. I know that if I add this code:
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 39, height: 39))
imageView.contentMode = .scaleAspectFit
let image = UIImage(named: "logo")
imageView.image = image
navigationItem.titleView = imageView
to ViewController, image with logo will appear but I want complete class which I will connect somewhere (for example, in Interface Builder) and it will make a beautiful Navigation Controller with buttons, images and etc. Moreover, I can create a different class with another Navigation Controller and connect it to different screens, on which, for example, I will not need images, only buttons. So question is: how to make custom Navigation Controller class?
I will be really glad and thankful for any help, Thank You!
Now, I know the answer! It will be written for Swift 4.1.2. Each class inherited from UIViewController has a property self.navigationController?.navigationBar or navigationItem. You can create you own class like this:
class CustomNavigationController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let color = UIColor(red: 81 / 255, green: 155 / 255, blue: 22 / 255, alpha: 1.0)
self.navigationController?.navigationBar.barTintColor = color
let image = UIImage(named: "logo")
let imageView = UIImageView(image: image)
imageView.contentMode = .scaleAspectFit
navigationItem.titleView = imageView
}
private func imageView(imageName: String) -> UIImageView {
let logo = UIImage(named: imageName)
let logoImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 35, height: 35))
logoImageView.contentMode = .scaleAspectFit
logoImageView.image = logo
logoImageView.widthAnchor.constraint(equalToConstant: 35).isActive = true
logoImageView.heightAnchor.constraint(equalToConstant: 35).isActive = true
return logoImageView
}
func barImageView(imageName: String) -> UIBarButtonItem {
return UIBarButtonItem(customView: imageView(imageName: imageName))
}
func barButton(imageName: String, selector: Selector) -> UIBarButtonItem {
let button = UIButton(type: .custom)
button.setImage(UIImage(named: imageName), for: .normal)
button.frame = CGRect(x: 0, y: 0, width: 35, height: 35)
button.widthAnchor.constraint(equalToConstant: 35).isActive = true
button.heightAnchor.constraint(equalToConstant: 35).isActive = true
button.addTarget(self, action: selector, for: .touchUpInside)
return UIBarButtonItem(customView: button)
}
}
Now you can inherit any of you UIViewControllers from this class CustomNavigationController and add ass much buttons, icons, change colors, logos and etc. as much as you want (of course, in normal range)! Like this:
class ViewController: CustomNavigationController {
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.leftBarButtonItem = barImageView(imageName: "picture1")
let firstImage = barImageView(imageName: "picture2")
let secondButton = barButton(imageName: "picture3", selector: #selector(secondButtonPressed))
navigationItem.rightBarButtonItems = [firstImage, secondButton]
}
#objc func secondButtonPressed() {
print("Pressed")
}
}
This is my solution, maybe not the best, but 100% works! Thank you!

Resources