Creating a button image with padding bottom in Swift - swift

I'm creating a system of Filters.
I'm trying to do that:
Currently I'm using the setBackgroundImage func, and I don't have this little rect below the picture:
filterButton.setBackgroundImage(imageForButton, for: .normal)
I tried functions like this one:
filterButton.contentEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 20, right: 0)
But it's not effective if I keep the setBackgroundImage func.
If I use setImage(), the results is good but the click is not effective any more.
filterButton.setImage(imageForButton, for: UIControlState.normal)
Do you have an idea to do what I would like ?
The full snippet:
for i in 0 ..< CIFilterNames.count {
itemCount = i
// Button properties
let filterButton = UIButton(type: .custom)
// filterButton.frame = CGRectMake(xCoord, yCoord, buttonWidth, buttonHeight)
filterButton.frame = CGRect(x: xCoord, y: yCoord, width: buttonWidth, height: buttonHeight)
filterButton.tag = itemCount
filterButton.addTarget(self, action: #selector(self.filterButtonTapped(_:)), for: .touchUpInside)
// filterButton.layer.cornerRadius = 6
// filterButton.clipsToBounds = true
let ciContext = CIContext(options: nil)
let coreImage = CIImage(image: originalImage.image!)
let filter = CIFilter(name: "\(CIFilterNames[i])" )
filter!.setDefaults()
filter!.setValue(coreImage, forKey: kCIInputImageKey)
let filteredImageData = filter!.value(forKey: kCIOutputImageKey) as! CIImage
let filteredImageRef = ciContext.createCGImage(filteredImageData, from: filteredImageData.extent)
let imageForButton = UIImage(cgImage: filteredImageRef!)
filterButton.backgroundColor = UIColor.red
filterButton.setImage(imageForButton, for: UIControlState.normal)
//filterButton.imageEdgeInsets = UIEdgeInsets(top: 0,left: 0,bottom: 15,right: 0)
//filterButton.setBackgroundImage(imageForButton, for: .normal)
filterButton.setTitle("A\(i)", for: .normal)
// filterButton.backgroundImage.contentMode = .scaleAspectFill
filterButton.setTitleColor(UIColor(red: 22 / 255, green: 21 / 255, blue: 22 / 255, alpha: 1), for: .normal)
filterButton.contentEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 20, right: 0)
filterButton.titleEdgeInsets.top = 140
// Add Buttons in the Scroll View
xCoord += buttonWidth + gapBetweenButtons
filtersScrollView.addSubview(filterButton)
}

It looks like you are trying to do everything in a single UIButton. I would suggest creating a custom element to handle this.
There are a few ways you could setup a custom UI element with the functionality that you want:
Use a UILabel to display the "A1" label with the colored background and display the image itself in a separate UIImageView. These could be overlaid with a invisible UIButton to receive the touchUpInside event and pass to your controller.
Alternatively, you could create a custom UIControl add add a UILabel and UIImageView as subviews. Disabling userInteraction on the subviews should allow the UIControl to receive the touch events. This could be contained in a single reusable class as if it's one UI element.

Related

Why does the selector, #objc, function add a subview, of my button image (as a UIImageView), in my UIButton?

I first declare the button
let balloon = UIButton()
A background image then gets added to the balloon
balloon.setBackgroundImage(UIImage(named:"balloon.jpg"), for: .normal)
An image view of the points get added to the balloon as a subview
subView = UIImageView(image: UIImage(named: "1") )
subView.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
subView.contentMode = UIView.ContentMode.scaleAspectFit
subView.center = CGPoint(x: balloon.frame.width/2, y: balloon.frame.height/2)
balloon.addSubview(subView)
I then use the addTarget function for the balloon
balloon.addTarget(self, action: #selector(pop), for: .touchUpInside)
After pop gets called (when the user taps on the balloon), the balloon now contains 2 subviews
- At index 0 of balloon.subviews, there is a UIImageView that is essentially the picture of the balloon with the same dimensions as the balloon button
- And the subs view that I added (aka the points)
here is how I found this problem in my addTarget function (pop):
#objc func pop(_ balloon: UIButton){
print("4.This is the balloon after calling pop \(balloon)")
print("5. This is the subview of the balloon after calling pop \(balloon.subviews)")
Ive added print statements in my function that verifies that the balloons are the same in both the pop func and my balloon creation func
I have already looked at the documentation for both UIButton and addTarget and neither of them have specified why the background image of the button gets created as a subview of the button when the selector func gets called
There shouldn't be that extra UIImageView in my UIButton since I never added that
What you're seeing has nothing to do with your selector / #objc func...
Many UIKit classes do a lot of "under-the-hood" work.
In the case of UIButton, subviews are only added as-needed.
For example, if this is all the code you execute:
let balloon = UIButton()
balloon.frame = CGRect(x: 100, y: 200, width: 240, height: 100)
view.addSubview(balloon)
The resulting button has Zero subviews.
If we do this:
let balloon = UIButton()
balloon.frame = CGRect(x: 100, y: 200, width: 240, height: 100)
view.addSubview(balloon)
balloon.setTitle("ABC", for: [])
The resulting button now has 1 subviews.
let balloon = UIButton()
balloon.frame = CGRect(x: 100, y: 200, width: 240, height: 100)
view.addSubview(balloon)
balloon.setTitle("ABC", for: [])
balloon.setBackgroundImage(img, for: .normal)
The resulting button now has 2 subviews.
let balloon = UIButton()
balloon.frame = CGRect(x: 100, y: 200, width: 240, height: 100)
view.addSubview(balloon)
balloon.setTitle("ABC", for: [])
balloon.setBackgroundImage(imgA, for: .normal)
balloon.addSubview(subview)
The resulting button now has 3 subviews.
let balloon = UIButton()
balloon.frame = CGRect(x: 100, y: 200, width: 240, height: 100)
view.addSubview(balloon)
balloon.setTitle("ABC", for: [])
balloon.setBackgroundImage(imgA, for: .normal)
balloon.addSubview(subview)
balloon.setImage(imgB, for: [])
And now we have 4 subviews.
Apple strongly discourages messing with the internals of UIButton. You might be better off creating a view subclass that contains a button and any additional subviews, rather than your current approach.
Worth Noting
the subviews may not be added until they are needed. So, if you set the title or image or background image in viewDidLoad(), those subviews will not be created until viewDidLayoutSubviews ... or even until the button is actually rendered.
the title label may be created even when you don't expect it to. For example, if the only thing you do to the button is constrain its position, it will get a title label. However, if you set the background image, the title label will be omitted.
Here is a quick example to demonstrate some of the resulting subview counts:
class BtnVC: UIViewController {
var buttons: [UIButton] = []
let stack = UIStackView()
let infoLabel: UILabel = {
let v = UILabel()
v.numberOfLines = 0
v.font = .monospacedSystemFont(ofSize: 14, weight: .regular)
return v
}()
var didLoadStr: String = ""
var didLayoutStr: String = ""
var didAppearStr: String = ""
override func viewDidLoad() {
super.viewDidLoad()
let largeFont = UIFont.systemFont(ofSize: 32)
let configuration = UIImage.SymbolConfiguration(font: largeFont) // <1>
guard let img2 = UIImage(systemName: "02.circle.fill", withConfiguration: configuration),
let img3 = UIImage(systemName: "03.circle.fill"),
let img4 = UIImage(systemName: "04.circle.fill")
else {
return
}
var b: UIButton!
b = UIButton()
buttons.append(b)
b = UIButton()
b.setTitle("1", for: [])
buttons.append(b)
b = UIButton()
b.setTitle("1", for: [])
b.setImage(img2, for: [])
buttons.append(b)
b = UIButton()
b.setTitle("1", for: [])
b.setImage(img2, for: [])
b.setBackgroundImage(img3.withTintColor(.systemGreen, renderingMode: .alwaysOriginal), for: [])
buttons.append(b)
b = UIButton()
b.setTitle("1", for: [])
b.setImage(img2, for: [])
b.setBackgroundImage(img3.withTintColor(.systemGreen, renderingMode: .alwaysOriginal), for: [])
let v = UIImageView(image: img4)
v.tintColor = .systemRed
v.frame = CGRect(x: 0, y: 0, width: 32, height: 32)
b.addSubview(v)
buttons.append(b)
stack.axis = .vertical
stack.spacing = 8
stack.distribution = .fillEqually
stack.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stack)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
stack.topAnchor.constraint(equalTo: g.topAnchor, constant: 120.0),
stack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
stack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
stack.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
])
// add this first button *without* auto-layout
buttons[0].frame = CGRect(x: 80, y: 40, width: 200, height: 50)
view.addSubview(buttons[0])
// add the rest of the buttons to the stack view
buttons.forEach { v in
v.backgroundColor = .systemYellow
v.setTitleColor(.black, for: [])
if v != buttons.first {
stack.addArrangedSubview(v)
}
}
stack.addArrangedSubview(infoLabel)
var s = "didLoad : "
buttons.forEach { v in
s += "\(v.subviews.count) / "
}
didLoadStr += s + "\n"
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// first button is not using auto-layout constraints, so
// let's size it to match the buttons in the stack view
// and posiiton it 8-pts above the stack view
var r = buttons[1].bounds
r.origin.y = stack.frame.origin.y - r.height - 8.0
r.origin.x = stack.frame.origin.x
buttons[0].frame = r
var s = "didLayout: "
buttons.forEach { v in
s += "\(v.subviews.count) / "
}
didLayoutStr += s + "\n"
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
var s = "didAppear: "
buttons.forEach { v in
s += "\(v.subviews.count) / "
}
didAppearStr += s + "\n"
infoLabel.text = didLoadStr + didLayoutStr + didAppearStr
}
}
That code will create 5 buttons, with each successive button getting another subview - title, image, backgroundImage, addSubview - and the "Info Label" at the bottom will show the subviews count at each stage (as is often the case, didLayoutSubviews() is called more than once):

Centering buttons programatically in Swift

I am trying to center Google Sign In and Sign Out buttons programmatically. In order to put both of them in the third quarter. I create 2 views that I wanted to put buttons to their center, in Storyboard.
GIDSignIn.sharedInstance()?.presentingViewController = self
GIDSignIn.sharedInstance().signIn()
let gSignIn = GIDSignInButton(frame: CGRect(x: 0, y: 0, width: loginView.frame.size.width, height: loginView.frame.size.height))
loginView.addSubview(gSignIn)
gSignIn.center = loginView.center
let gSignOut = UIButton(frame: CGRect(x: 0, y: 0, width: signOutView.frame.size.width, height: signOutView.frame.size.height))
gSignOut.backgroundColor = UIColor.white.withAlphaComponent(0)
gSignOut.setTitle("Sign Out", for: .normal)
gSignOut.setTitleColor(UIColor.red, for: .normal)
gSignOut.addTarget(self, action: #selector(self.signOut(_:)), for: .touchUpInside)
gSignOut.center = signOutView.center
self.signOutView.addSubview(gSignOut)
As you can see, also I am trying to resize buttons according to view size which helps me to resize buttons depend on device size.
Here is the second half of my storyboard.
Here is the simulator screen when I run the code.
Thanks.
Use auto-layout instead of setting frame.center, it will be more flexible.
Autolayout goes like below code.
GIDSignIn.sharedInstance()?.presentingViewController = self
GIDSignIn.sharedInstance().signIn()
let gSignIn = GIDSignInButton(frame: CGRect(x: 0, y: 0, width: loginView.frame.size.width, height: loginView.frame.size.height))
gSignIn.translatesautoresizingmaskintoconstraints = false
loginView.addSubview(gSignIn)
NSLayoutConstraint.activate([
gSignIn.leftAnchor.constraint(equalTo: loginView.leftAnchor),
gSignIn.rightAnchor.constraint(equalTo: loginView.rightAnchor),
gSignIn.topAnchor.constraint(equalTo: loginView.topAnchor),
gSignIn.bottomAnchor.constraint(equalTo: loginView.bottomAnchor)
])

How do I place the stepper value between the - and + signs?

I am programatically trying to customize a UIStepper. I'd like to place the stepper.value between the - and + buttons and I want to change the background of the stepper to white.
How would I do that?
Here is what I have that does not work:
let stepper = UIStepper(frame: CGRect(x: 0, y: 0, width: 200, height: 40))
stepper.minimumValue = 0
stepper.center = self.contentView.center
stepper.backgroundColor = .white
stepper.layer.backgroundColor = UIColor.white.cgColor
stepper.stepValue = 1
stepper.tintColor = UIColor(hex: Constants.Colors.primary)
stepper.setIncrementImage(UIImage(named: "icon_cart_plus"), for: .normal)
stepper.setDecrementImage(UIImage(named: "icon_cart_minus"), for: .normal)

Swift UIButton Image size distorted in BarButtonItem

I want to add an UIButton to the navigationItem.leftBarButtonItem.
I want the UIButton to be 24x24px and scale the Image down.
But the Button got distorted... If I change the UIImage to a smaller image everything is fine. -> see pics
What can I do apart from the scaleAspectFit ??
let userProfilePic = UIButton()
userProfilePic.frame = CGRect(x: 0, y: 0, width: 24, height: 24)
userProfilePic.setImage(UIImage(named: "profile_icon.jpg"), for: .normal)
userProfilePic.contentMode = .scaleAspectFit
userProfilePic.clipsToBounds = true
userProfilePic.layer.borderWidth = 0.5
userProfilePic.layer.borderColor = UIColor.white.cgColor
userProfilePic.layer.cornerRadius = (userProfilePic.frame.size.width) / 2
userProfilePic.addTarget(self, action: #selector(goToSettings), for: UIControlEvents.touchUpInside)
self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: userProfilePic)
Thank you!
Try this code
The is problem appears on ios 11+ , UIBarButtonItem use autolayout on ios 11+ other use frames
let userProfilePic = UIButton()
userProfilePic.frame = CGRect(x: 0, y: 0, width: 24, height: 24)
userProfilePic.setImage(UIImage(named: "profile_icon.jpg"), for: .normal)
userProfilePic.contentMode = .scaleToFill
userProfilePic.clipsToBounds = true
userProfilePic.layer.borderWidth = 0.5
userProfilePic.layer.borderColor = UIColor.white.cgColor
userProfilePic.layer.cornerRadius = (userProfilePic.frame.size.width) / 2
if #available(iOS 11, *) {
userProfilePic.widthAnchor.constraint(equalToConstant: 24.0).isActive = true
userProfilePic.heightAnchor.constraint(equalToConstant: 24.0).isActive = true
}
userProfilePic.addTarget(self, action: #selector(goToSettings), for: UIControlEvents.touchUpInside)
self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: userProfilePic)

resolving the issue coming with text field and button image issue

I am new in swift.I have created simple login screen .I have two issues coming with loginviewController.swift
Issue 1 # i dont know how to compare the two image that is displayed in button . I have used if checkbox.setImage(img, for: .normal) for comparing image that is displayed in side button for toggle action between checked and unchecked
let img = UIImage(named:"check box#1x.png")
let img2 = UIImage(named:"uncheck box.png")
#IBOutlet weak var checkbox: UIButton!
#IBAction func checkbox(_ sender: Any) {
if checkbox.setImage(img, for: .normal)
{
checkbox.setImage(img , for: .normal)
}
else{
checkbox.setImage(img2, for: .normal)
}
}
Issue 2 # I am trying to high light the bottom borders of text field .i am writing the code for highlighting two text field but it is highlighting only single text field
func boaderSetting() {
let border = CALayer()
let width = CGFloat(1.0)
border.borderColor = UIColor.orange.cgColor
border.frame = CGRect(x: 0, y: username_input.frame.size.height - width, width: username_input.frame.size.width, height: username_input.frame.size.height)
border.borderWidth = width
username_input.layer.addSublayer(border)
username_input.layer.masksToBounds = true
///
let border1 = CALayer()
let width1 = CGFloat(1.0)
border1.borderColor = UIColor.orange.cgColor
border1.frame = CGRect(x: 0, y: password_input.frame.size.height - width1, width: password_input.frame.size.width, height: password_input.frame.size.height)
border1.borderWidth = width1
password_input.layer.addSublayer(border)
password_input.layer.masksToBounds = true
}
how to
->compare image displayed in button with other image
-> hight light the bottom border of both text field .
you can download the project from this link https://drive.google.com/file/d/1zjNUBXZ-9WL4DTglhMXIlN-TpaO5IXBz/view?usp=sharing
To check against an image in the button you can use button.currentImage == image. In your example it will look like this
#IBAction func checkbox(_ sender: Any) {
if checkbox.currentImage == img2 {
checkbox.setImage(img , for: .normal)
} else {
checkbox.setImage(img2, for: .normal)
}
}
It is only showing a highlight on the bottom text field border as there is a mistake in your example here password_input.layer.addSublayer(border), you want to use border1 here instead.
func boaderSetting() {
let border = CALayer()
let width = CGFloat(1.0)
border.borderColor = UIColor.orange.cgColor
border.frame = CGRect(x: 0, y: username_input.frame.size.height - width, width: username_input.frame.size.width, height: username_input.frame.size.height)
border.borderWidth = width
username_input.layer.addSublayer(border)
username_input.layer.masksToBounds = true
///
let border1 = CALayer()
let width1 = CGFloat(1.0)
border1.borderColor = UIColor.orange.cgColor
border1.frame = CGRect(x: 0, y: password_input.frame.size.height - width1, width: password_input.frame.size.width, height: password_input.frame.size.height)
border1.borderWidth = width1
password_input.layer.addSublayer(border1)
password_input.layer.masksToBounds = true
}
For #1 you can simply set images for different states.
button.setImage(UIImage(named: "imageForSelected"), for : .selected)
button.setImage(UIImage(named: "imageForNotSelected"), for : .normal)
And then somewhere in your logic set button state
button.setSelected(true)