Slider with custom thumb image and text - swift

Hy,
I'm trying to customize a slider by changing the thumb image and add a label over the picture.
For this, in my view in I set the image for slider thumb:
class SliderView: NibLoadingView {
#IBOutlet weak var slider: ThumbTextSlider!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
contentView = legacyLoadXib()
setup()
}
override func setup() {
super.setup()
self.slider.setThumbImage(UIImage(named: "onb_cc_slider_thumb")!, for: .normal)
self.slider.thumbTextLabel.font = UIFont(name: Fonts.SanFranciscoDisplayRegular, size: 14)
}
}
In ThumbTextSlider class I set the text label as below:
class ThumbTextSlider: UISlider {
var thumbTextLabel: UILabel = UILabel()
private var thumbFrame: CGRect {
return thumbRect(forBounds: bounds, trackRect: trackRect(forBounds: bounds), value: value)
}
override func layoutSubviews() {
super.layoutSubviews()
thumbTextLabel.frame = thumbFrame
}
override func awakeFromNib() {
super.awakeFromNib()
addSubview(thumbTextLabel)
thumbTextLabel.layer.zPosition = layer.zPosition + 1
}
}
When I made test the result was a little different.
How, can I fix the issue ?
The expected result:
Kind regards

This class may help you. In class instead of image I created image you can replace with you thumb image.
class ThumbTextSlider: UISlider {
private var thumbTextLabel: UILabel = UILabel()
private var thumbFrame: CGRect {
return thumbRect(forBounds: bounds, trackRect: trackRect(forBounds: bounds), value: value)
}
private lazy var thumbView: UIView = {
let thumb = UIView()
return thumb
}()
override func layoutSubviews() {
super.layoutSubviews()
thumbTextLabel.frame = CGRect(x: thumbFrame.origin.x, y: thumbFrame.origin.y, width: thumbFrame.size.width, height: thumbFrame.size.height)
self.setValue()
}
private func setValue() {
thumbTextLabel.text = self.value.description
}
override func awakeFromNib() {
super.awakeFromNib()
addSubview(thumbTextLabel)
thumbTextLabel.textAlignment = .center
thumbTextLabel.textColor = .blue
thumbTextLabel.adjustsFontSizeToFitWidth = true
thumbTextLabel.layer.zPosition = layer.zPosition + 1
let thumb = thumbImage()
setThumbImage(thumb, for: .normal)
}
private func thumbImage() -> UIImage {
let width = 100
thumbView.frame = CGRect(x: 0, y: 15, width: width, height: 30)
thumbView.layer.cornerRadius = 15
let renderer = UIGraphicsImageRenderer(bounds: thumbView.bounds)
return renderer.image { rendererContext in
rendererContext.cgContext.setShadow(offset: .zero, blur: 5, color: UIColor.black.cgColor)
thumbView.backgroundColor = .red
thumbView.layer.render(in: rendererContext.cgContext)
}
}
override func trackRect(forBounds bounds: CGRect) -> CGRect {
return CGRect(origin: bounds.origin, size: CGSize(width: bounds.width, height: 5))
}
}

Related

Why does this logic work well (UICollectionViewCell + CustomPaddingLabel)

When I make a message UI, need a dynamic height UICollectionViewCell which be calculated with label's height. But first try the cell when reuse, customLabel's layout did not change so the cell is maintained cell's layout which previously used.
I found that the problem is CustomPaddingLabel and make it working well. But I still can't understand why this logic make working well. Read layout life cylcle, UICollectionView life cycle, but that can't solve this question.
plz, explain why this logic can make working well, also why CustomPaddingLabel cause this problem. (When I try with UILabel, this problem does not occured)
This is UICollectionViewCell...
final class MessageCell: UICollectionViewCell {
static let identifier: String = .init(describing: MessageCell.self)
private let messageLabel: PaddingLabel = .init().then {
$0.numberOfLines = 0
$0.textColor = .black
$0.font = .systemFont(ofSize: 14.5)
$0.lineBreakStrategy = .pushOut
$0.layer.cornerRadius = 14
$0.clipsToBounds = true
$0.setContentHuggingPriority(.defaultLow, for: .horizontal)
$0.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
}
private let dateLabel = UILabel().then {
$0.font = .systemFont(ofSize: 10, weight: .regular)
$0.textColor = .secondaryLabel
$0.numberOfLines = 1
}
private var chatType: ChatType = .none
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubviews(messageLabel, dateLabel)
}
#available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("Does not use this initializer")
}
override func layoutSubviews() {
super.layoutSubviews()
}
override func prepareForReuse() {
super.prepareForReuse()
messageLabel.text = nil
dateLabel.text = nil
chatType = .none
self.messageLabel.snp.removeConstraints()
self.dateLabel.snp.removeConstraints()
self.messageLabel.layoutIfNeeded()
}
func setCell(with message: String, type: ChatType, dateString: String) {
self.messageLabel.text = message
self.chatType = type
self.dateLabel.text = dateString
self.configureLayouts()
self.configureProperties()
}
}
private extension MessageCell {
func configureLayouts() {
switch chatType {
case .none:
break
case .send:
messageLabel.snp.remakeConstraints { make in
make.top.bottom.equalToSuperview()
make.trailing.equalToSuperview().inset(20)
make.leading.greaterThanOrEqualToSuperview()
}
dateLabel.snp.remakeConstraints { make in
make.trailing.equalTo(messageLabel.snp.leading).offset(-4)
make.leading.greaterThanOrEqualToSuperview().inset(56)
make.bottom.equalTo(messageLabel)
}
case .receive:
messageLabel.snp.remakeConstraints { make in
make.top.bottom.equalToSuperview()
make.leading.equalToSuperview().inset(56)
make.trailing.lessThanOrEqualToSuperview()
}
dateLabel.snp.remakeConstraints { make in
make.leading.equalTo(messageLabel.snp.trailing).offset(4)
make.trailing.lessThanOrEqualToSuperview().inset(24)
make.bottom.equalTo(messageLabel)
}
}
}
func configureProperties() {
switch chatType {
case .none:
break
case .send:
messageLabel.backgroundColor = UIColor(red: 250/255, green: 230/255, blue: 76/255, alpha: 1)
case .receive:
messageLabel.backgroundColor = .white
}
}
}
and this is CustomPaddingLabel...
class PaddingLabel: UILabel {
private var topInset: CGFloat
private var bottomInset: CGFloat
private var leftInset: CGFloat
private var rightInset: CGFloat
override var intrinsicContentSize: CGSize {
let size = super.intrinsicContentSize
return CGSize(width: size.width + leftInset + rightInset, height: size.height + topInset + bottomInset)
}
override var bounds: CGRect {
didSet {
preferredMaxLayoutWidth = bounds.width - (leftInset + rightInset)
}
}
init(topInset: CGFloat = 8, bottomInset: CGFloat = 8, leftInset: CGFloat = 10, rightInset: CGFloat = 10) {
self.topInset = topInset
self.bottomInset = bottomInset
self.leftInset = leftInset
self.rightInset = rightInset
super.init(frame: .zero)
}
#available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("Does not use this initializer")
}
override func drawText(in rect: CGRect) {
let insets = UIEdgeInsets(top: topInset, left: leftInset, bottom: bottomInset, right: rightInset)
super.drawText(in: rect.inset(by: insets))
}
}
Configure the cell height of the UICollectionVIew to dynamic according to the CustomPaddingLabel's height. But when the cell be reused, CustomPaddingLabel's layout is concreted so the next cell has unbalance fit layout.
I found the problem is from CustomPaddingLabel (use UILabel this problem doesn't happen), and fix it worked well. But I don't know why this problem was occured and why this logic solved that problem.
plz, explain this question.

Custom Floating UITextField animation

everyone. I'm trying to make a floating textfield. I couldn't somehow move the placeholderLabel up the animation when the TextField is clicked.
I just tried to change the testPlaceholder.centerYAnchor but without success..
Can I develop TextField as Nib(xib)?
If I enhance TextField as Nib(xib) I can define IBOutlet as NSLayoutConstraint and animate with UIView I can change anchor in an animated way.
Finally, I want to reduce the font's size in an animated way.
CustomTextField:
class TestTextField: UITextField {
lazy var testPlaceholder: UILabel = {
let label = UILabel()
label.text = "placeholder"
return label
}()
lazy var centerConstraint: NSLayoutConstraint = {
let constraint = NSLayoutConstraint()
return constraint
}()
let padding = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
override open func textRect(forBounds bounds: CGRect) -> CGRect {
return bounds.inset(by: padding)
}
override open func placeholderRect(forBounds bounds: CGRect) -> CGRect {
return bounds.inset(by: padding)
}
override open func editingRect(forBounds bounds: CGRect) -> CGRect {
return bounds.inset(by: padding)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
self.addSubview(testPlaceholder)
addConstraint()
self.delegate = self
}
func addConstraint() {
testPlaceholder.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
testPlaceholder.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 6),
testPlaceholder.centerYAnchor.constraint(equalTo: self.centerYAnchor)
])
}
func placeHolderUp() {
UIView.animate(withDuration: 1) {
self.testPlaceholder.transform = CGAffineTransform(translationX: 0, y: -18)
}
self.setNeedsLayout()
self.layoutIfNeeded()
}
func activeTextFieldColor() {
self.layer.borderColor = UIColor.green.cgColor
placeHolderUp()
}
func deactiveTextFieldColor() {
self.layer.borderColor = UIColor.red.cgColor
}
}
extension TestTextField: UITextFieldDelegate {
func textFieldDidBeginEditing(_ textField: UITextField) {
self.layer.borderColor = UIColor.green.cgColor
self.textColor = .green
}
func textFieldDidEndEditing(_ textField: UITextField) {
self.layer.borderColor = UIColor.red.cgColor
self.textColor = .red
}
}

How to make a Self-sizing UiImageView?

I have a need for a simple QR Code class that I can re-use. I have created the class and it works, however manually setting the size constraints is not desired because it needs to adjust its size based on the DPI of the device. Here in this minimal example, I just use 100 as the sizing calculation code is not relevant (set to 50 in IB). Also I will have multiple QR Codes in different positions, which I will manage their positioning by IB. But at least I hope to be able to set the width and height constraints in code.
The below code shows a QR code, in the right size (set at runtime), but when the constraints are set to horizontally and vertically center it, it does not. Again, I don't want the size constraints in the IB, but I do want the position constraints in the IB
import Foundation
import UIKit
#IBDesignable class QrCodeView: UIImageView {
var content:String = "test" {
didSet {
generateCode(content)
}
}
lazy var filter = CIFilter(name: "CIQRCodeGenerator")
lazy var imageView = UIImageView()
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override func layoutSubviews() {
super.layoutSubviews()
imageView.frame = CGRect(x:0, y:0, width:100, height:100)
frame = CGRect(x:frame.origin.x, y:frame.origin.y, width:100, height:100)
}
func setup() {
//translatesAutoresizingMaskIntoConstraints = false
generateCode(content)
addSubview(imageView)
layoutIfNeeded()
}
func generateCode(_ string: String) {
guard let filter = filter,
let data = string.data(using: .isoLatin1, allowLossyConversion: false) else {
return
}
filter.setValue(data, forKey: "inputMessage")
guard let ciImage = filter.outputImage else {
return
}
let transform = CGAffineTransform(scaleX: 10, y: 10)
let scaled = UIImage(ciImage: ciImage.transformed(by: transform))
imageView.image = scaled
}
}
I believe you're making this more complicated than need be...
Let's start with a simple #IBDesignable UIImageView subclass.
Start with a new project and add this code:
#IBDesignable
class MyImageView: UIImageView {
// we'll use this later
var myIntrinsicSize: CGSize = CGSize(width: 100.0, height: 100.0)
override var intrinsicContentSize: CGSize {
return myIntrinsicSize
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setup()
self.image = UIImage()
}
func setup() {
backgroundColor = .green
contentMode = .scaleToFill
}
}
Now, in Storyboard, add a UIImageView to a view controller. Set its custom class to MyImageView and set Horizontal and Vertical Center constraints.
The image view should automatically size itself to 100 x 100, centered in the view with a green background (we're just setting the background so we can see it):
Run the app, and you should see the same thing.
Now, add it as an #IBOutlet to a view controller:
class ViewController: UIViewController {
#IBOutlet var testImageView: MyImageView!
override func viewDidLoad() {
super.viewDidLoad()
testImageView.myIntrinsicSize = CGSize(width: 300.0, height: 300.0)
}
}
Run the app, and you will see a centered green image view, but now it will be 300 x 300 points instead of 100 x 100.
The rest of your task is pretty much adding code to set this custom class's .image property once you've rendered the QRCode image.
Here's the custom class:
#IBDesignable
class QRCodeView: UIImageView {
// so we can test changing the QRCode content in IB
#IBInspectable
var content:String = "test" {
didSet {
generateCode(content)
}
}
var qrIntrinsicSize: CGSize = CGSize(width: 100.0, height: 100.0)
override var intrinsicContentSize: CGSize {
return qrIntrinsicSize
}
lazy var filter = CIFilter(name: "CIQRCodeGenerator")
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setup()
generateCode(content)
}
func setup() {
contentMode = .scaleToFill
}
override func layoutSubviews() {
super.layoutSubviews()
generateCode(content)
}
func generateCode(_ string: String) {
guard let filter = filter,
let data = string.data(using: .isoLatin1, allowLossyConversion: false) else {
return
}
filter.setValue(data, forKey: "inputMessage")
guard let ciImage = filter.outputImage else {
return
}
let scX = bounds.width / ciImage.extent.size.width
let scY = bounds.height / ciImage.extent.size.height
let transform = CGAffineTransform(scaleX: scX, y: scY)
let scaled = UIImage(ciImage: ciImage.transformed(by: transform))
self.image = scaled
}
}
In Storyboard / IB:
And here's an example view controller:
class ViewController: UIViewController {
#IBOutlet var qrCodeView: QRCodeView!
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// calculate your needed size
// I'll assume it ended up being 240 x 240
qrCodeView.qrIntrinsicSize = CGSize(width: 240.0, height: 240.0)
}
}
Edit
Here's a modified QRCodeView class that will size itself to a (physical) 15x15 mm image.
I used DeviceKit from https://github.com/devicekit/DeviceKit to get the current device's ppi. See the comment to replace it with your own (assuming you are already using something else).
When this class is instantiated, it will:
get the current device's ppi
convert ppi to pixels-per-millimeter
calculate 15 x pixels-per-millimeter
convert based on screen scale
update its intrinsic size
The QRCodeView (subclass of UIImageView) needs only position constraints... so you can use Top + Leading, Top + Trailing, Center X & Y, Bottom + CenterX, etc, etc.
#IBDesignable
class QRCodeView: UIImageView {
#IBInspectable
var content:String = "test" {
didSet {
generateCode(content)
}
}
var qrIntrinsicSize: CGSize = CGSize(width: 100.0, height: 100.0)
override var intrinsicContentSize: CGSize {
return qrIntrinsicSize
}
lazy var filter = CIFilter(name: "CIQRCodeGenerator")
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setup()
generateCode(content)
}
func setup() {
contentMode = .scaleToFill
// using DeviceKit from https://github.com/devicekit/DeviceKit
// replace with your lookup code that gets
// the device's ppi
let device = Device.current
guard let ppi = device.ppi else { return }
// convert to pixels-per-millimeter
let ppmm = CGFloat(ppi) / 25.4
// we want 15mm size
let mm15 = 15.0 * ppmm
// convert based on screen scale
let mmScale = mm15 / UIScreen.main.scale
// update our intrinsic size
self.qrIntrinsicSize = CGSize(width: mmScale, height: mmScale)
}
override func layoutSubviews() {
super.layoutSubviews()
generateCode(content)
}
func generateCode(_ string: String) {
guard let filter = filter,
let data = string.data(using: .isoLatin1, allowLossyConversion: false) else {
return
}
filter.setValue(data, forKey: "inputMessage")
guard let ciImage = filter.outputImage else {
return
}
let scX = bounds.width / ciImage.extent.size.width
let scY = bounds.height / ciImage.extent.size.height
let transform = CGAffineTransform(scaleX: scX, y: scY)
let scaled = UIImage(ciImage: ciImage.transformed(by: transform))
self.image = scaled
}
}

How can I position these UIView elements from the right using CGRect to position

I have a UIView sub class that allows me to create a group of 'tags' for the footer of some content. At the moment however they are position aligned to the left edge, I would like them to be positioned from the right.
I have included a playground below that should run the screen shot you can see.
The position is set within the layoutSubviews method of CloudTagView.
I tried to play around with their position but have not been able to start them from the right however.
import UIKit
import PlaygroundSupport
// CLOUD VIEW WRAPPER - THIS IS THE CONTAINER FOR THE TAGS AND SETS UP THEIR FRAME
class CloudTagView: UIView {
weak var delegate: TagViewDelegate?
override var intrinsicContentSize: CGSize {
return frame.size
}
var removeOnDismiss = true
var resizeToFit = true
var tags = [TagView]() {
didSet {
layoutSubviews()
}
}
var padding = 5 {
didSet {
layoutSubviews()
}
}
var maxLengthPerTag = 0 {
didSet {
layoutSubviews()
}
}
public override init(frame: CGRect) {
super.init(frame: frame)
isUserInteractionEnabled = true
clipsToBounds = true
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
isUserInteractionEnabled = true
clipsToBounds = true
}
override func layoutSubviews() {
for tag in subviews {
tag.removeFromSuperview()
}
var xAxis = padding
var yAxis = padding
var maxHeight = 0
for (index, tag) in tags.enumerated() {
setMaxLengthIfNeededIn(tag)
tag.delegate = self
if index == 0 {
maxHeight = Int(tag.frame.height)
}else{
let expectedWidth = xAxis + Int(tag.frame.width) + padding
if expectedWidth > Int(frame.width) {
yAxis += maxHeight + padding
xAxis = padding
maxHeight = Int(tag.frame.height)
}
if Int(tag.frame.height) > maxHeight {
maxHeight = Int(tag.frame.height)
}
}
tag.frame = CGRect(x: xAxis, y: yAxis, width: Int(tag.frame.size.width), height: Int(tag.frame.size.height))
addSubview(tag)
tag.layoutIfNeeded()
xAxis += Int(tag.frame.width) + padding
}
if resizeToFit {
frame = CGRect(x: frame.origin.x, y: frame.origin.y, width: frame.size.width, height: CGFloat(yAxis + maxHeight + padding))
}
}
// MARK: Methods
fileprivate func setMaxLengthIfNeededIn(_ tag: TagView) {
if maxLengthPerTag > 0 && tag.maxLength != maxLengthPerTag {
tag.maxLength = maxLengthPerTag
}
}
}
// EVERYTHING BELOW HERE IS JUST SETUP / REQUIRED TO RUN IN PLAYGROUND
class ViewController:UIViewController{
let cloudView: CloudTagView = {
let view = CloudTagView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
let tags = ["these", "are", "my", "tags"]
tags.forEach { tag in
let t = TagView(text: tag)
t.backgroundColor = .darkGray
t.tintColor = .white
cloudView.tags.append(t)
}
view.backgroundColor = .white
view.addSubview(cloudView)
NSLayoutConstraint.activate([
cloudView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
cloudView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
cloudView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
cloudView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor)
])
}
}
// Tag View
class TagView: UIView {
weak var delegate: TagViewDelegate?
var text = "" {
didSet {
layoutSubviews()
}
}
var marginTop = 5 {
didSet {
layoutSubviews()
}
}
var marginLeft = 10 {
didSet {
layoutSubviews()
}
}
var iconImage = UIImage(named: "close_tag_2", in: Bundle(for: CloudTagView.self), compatibleWith: nil) {
didSet {
layoutSubviews()
}
}
var maxLength = 0 {
didSet {
layoutSubviews()
}
}
override var backgroundColor: UIColor? {
didSet {
layoutSubviews()
}
}
override var tintColor: UIColor? {
didSet {
layoutSubviews()
}
}
var font: UIFont = UIFont.systemFont(ofSize: 12) {
didSet {
layoutSubviews()
}
}
fileprivate let dismissView: UIView
fileprivate let icon: UIImageView
fileprivate let textLabel: UILabel
public override init(frame: CGRect) {
dismissView = UIView()
icon = UIImageView()
textLabel = UILabel()
super.init(frame: frame)
isUserInteractionEnabled = true
addSubview(textLabel)
addSubview(icon)
addSubview(dismissView)
dismissView.isUserInteractionEnabled = true
textLabel.isUserInteractionEnabled = true
dismissView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(TagView.iconTapped)))
textLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(TagView.labelTapped)))
backgroundColor = UIColor(white: 0.0, alpha: 0.6)
tintColor = UIColor.white
}
public required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public init(text: String) {
dismissView = UIView()
icon = UIImageView()
textLabel = UILabel()
super.init(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
isUserInteractionEnabled = true
addSubview(textLabel)
addSubview(icon)
addSubview(dismissView)
dismissView.isUserInteractionEnabled = true
textLabel.isUserInteractionEnabled = true
dismissView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(TagView.iconTapped)))
textLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(TagView.labelTapped)))
self.text = text
backgroundColor = UIColor(white: 0.0, alpha: 0.6)
tintColor = UIColor.white
}
override func layoutSubviews() {
icon.frame = CGRect(x: marginLeft, y: marginTop + 4, width: 8, height: 8)
icon.image = iconImage?.withRenderingMode(.alwaysTemplate)
icon.tintColor = tintColor
let textLeft: Int
if icon.image != nil {
dismissView.isUserInteractionEnabled = true
textLeft = marginLeft + Int(icon.frame.width ) + marginLeft / 2
} else {
dismissView.isUserInteractionEnabled = false
textLeft = marginLeft
}
textLabel.frame = CGRect(x: textLeft, y: marginTop, width: 100, height: 20)
textLabel.backgroundColor = UIColor(white: 0, alpha: 0.0)
if maxLength > 0 && text.count > maxLength {
textLabel.text = text.prefix(maxLength)+"..."
}else{
textLabel.text = text
}
textLabel.textAlignment = .center
textLabel.font = font
textLabel.textColor = tintColor
textLabel.sizeToFit()
let tagHeight = Int(max(textLabel.frame.height,14)) + marginTop * 2
let tagWidth = textLeft + Int(max(textLabel.frame.width,14)) + marginLeft
let dismissLeft = Int(icon.frame.origin.x) + Int(icon.frame.width) + marginLeft / 2
dismissView.frame = CGRect(x: 0, y: 0, width: dismissLeft, height: tagHeight)
frame = CGRect(x: Int(frame.origin.x), y: Int(frame.origin.y), width: tagWidth, height: tagHeight)
layer.cornerRadius = bounds.height / 2
}
// MARK: Actions
#objc func iconTapped(){
delegate?.tagDismissed?(self)
}
#objc func labelTapped(){
delegate?.tagTouched?(self)
}
}
// MARK: TagViewDelegate
#objc protocol TagViewDelegate {
#objc optional func tagTouched(_ tag: TagView)
#objc optional func tagDismissed(_ tag: TagView)
}
extension CloudTagView: TagViewDelegate {
public func tagDismissed(_ tag: TagView) {
delegate?.tagDismissed?(tag)
if removeOnDismiss {
if let index = tags.firstIndex(of: tag) {
tags.remove(at: index)
}
}
}
public func tagTouched(_ tag: TagView) {
delegate?.tagTouched?(tag)
}
}
let viewController = ViewController()
PlaygroundPage.current.liveView = viewController
PlaygroundPage.current.needsIndefiniteExecution = true
UIStackView can line subviews up in a row for you, including with trailing alignment. Here is a playground example:
import SwiftUI
import PlaygroundSupport
class V: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let tags = ["test", "testing", "test more"].map { word -> UIView in
let label = UILabel()
label.text = word
label.translatesAutoresizingMaskIntoConstraints = false
let background = UIView()
background.backgroundColor = .cyan
background.layer.cornerRadius = 8
background.clipsToBounds = true
background.addSubview(label)
NSLayoutConstraint.activate([
background.centerXAnchor.constraint(equalTo: label.centerXAnchor),
background.centerYAnchor.constraint(equalTo: label.centerYAnchor),
background.widthAnchor.constraint(equalTo: label.widthAnchor, constant: 16),
background.heightAnchor.constraint(equalTo: label.heightAnchor, constant: 16),
])
return background
}
let stack = UIStackView.init(arrangedSubviews: [UIView()] + tags)
stack.translatesAutoresizingMaskIntoConstraints = false
stack.axis = .horizontal
stack.alignment = .trailing
stack.spacing = 12
view.addSubview(stack)
NSLayoutConstraint.activate([
stack.topAnchor.constraint(equalTo: view.topAnchor),
stack.widthAnchor.constraint(equalTo: view.widthAnchor),
])
view.backgroundColor = .white
}
}
PlaygroundPage.current.liveView = V()

How to set corner radius to a collection of UIButtons

I'm new at using Xcode. My question is regarding "How to set corner.Radius for UIButtons (collection) instead of doing 1 by 1. Once the collection is created i'm using the following line:
self.myButtons.layer.cornerRadius = 10
but that is for a single button. Is it possible to do this for a "collection" of buttons?
enter image description here
any help is greatly appreciated.
#IBOutlet var myButtons: [UIButton]!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.myButtons.layer.conerRadius = 10
A few options:
Iterate through them yourself:
myButtons.forEach { $0.layer.cornerRadius = 10 }
Use NSArray and its setValue(_:forKey:)
(myButtons as NSArray).setValue(10, forKey: "cornerRadius")
I’d lean towards the former, but the latter is the old Objective-C way of doing it (which is why we had to bridge to NSArray).
The other approach is to define your own UIButton subclass, e.g. RoundedButton that does this for you. Just set the base class for your button in IB to be your custom subclass.
E.g. for fixed corner radius (which you can also adjust right in IB):
#IBDesignable
class RoundedButton: UIButton {
#IBInspectable var cornerRadius: CGFloat = 10 {
didSet {
layer.cornerRadius = cornerRadius
}
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
override init(frame: CGRect = .zero) {
super.init(frame: frame)
configure()
}
}
private extension RoundedButton {
func configure() {
layer.cornerRadius = cornerRadius
}
}
Or, if you want dynamic rounding based upon the height:
#IBDesignable
class RoundedButton: UIButton {
override func layoutSubviews() {
super.layoutSubviews()
let radius = min(bounds.width, bounds.height) / 2
layer.cornerRadius = radius
}
}
The virtue of this approach is that you can see your rounded buttons rendered right in IB.
Follow this below steps -
1.Choose UIButton from the object library
2.Drag to your storyboard
3.Choose border style as none.
4.Create a Swift file and add this below extension -
extension UIView {
#IBInspectable var cornerRadius: CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
layer.masksToBounds = newValue > 0
}
}
#IBInspectable var borderWidth: CGFloat {
get {
return layer.borderWidth
}
set {
layer.borderWidth = newValue
}
}
#IBInspectable var borderColor: UIColor? {
get {
return UIColor(cgColor: layer.borderColor!)
}
set {
layer.borderColor = newValue?.cgColor
}
}
}
extension UIButton {
func roundedButton(){
let maskPAth1 = UIBezierPath(roundedRect: self.bounds,
byRoundingCorners: [.topLeft , .topRight],
cornerRadii:CGSize(width:8.0, height:8.0))
let maskLayer1 = CAShapeLayer()
maskLayer1.frame = self.bounds
maskLayer1.path = maskPAth1.cgPath
self.layer.mask = maskLayer1
}
}
extension UITextField {
func setLeftPaddingPoints(_ amount:CGFloat){
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: amount, height: self.frame.size.height))
self.leftView = paddingView
self.leftViewMode = .always
}
func setRightPaddingPoints(_ amount:CGFloat) {
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: amount, height: self.frame.size.height))
self.rightView = paddingView
self.rightViewMode = .always
}
}
extension UITextField {
#IBInspectable var maxLength: Int {
get {
if let length = objc_getAssociatedObject(self, &kAssociationKeyMaxLength) as? Int {
return length
} else {
return Int.max
}
}
set {
objc_setAssociatedObject(self, &kAssociationKeyMaxLength, newValue, .OBJC_ASSOCIATION_RETAIN)
addTarget(self, action: #selector(checkMaxLength), for: .editingChanged)
}
}
#objc func checkMaxLength(textField: UITextField) {
guard let prospectiveText = self.text,
prospectiveText.count > maxLength
else {
return
}
let selection = selectedTextRange
let indexEndOfText = prospectiveText.index(prospectiveText.startIndex, offsetBy: maxLength)
let substring = prospectiveText[..<indexEndOfText]
text = String(substring)
selectedTextRange = selection
}
}
5.Now you can access this extensions either from storyboard or from code to change the values and see the effects.
6.You can change the corner radius, border width, border colour for UIView,UIButton,UITexfield.Try this Method.
Hope this method also helps.
/// Other way to set Corner radius of view; also inspectable from Storyboard.
public extension UIView {
#IBInspectable public var cornerRadius: CGFloat {
get {
return layer.cornerRadius
}
set {
// layer.masksToBounds = true
layer.cornerRadius = abs(CGFloat(Int(newValue * 100)) / 100)
}
}}
By usisng this you can set border radius from storyboard
OR
for btn in yourbuttonCollectionName {btn.cornerRadius = 10.0}
You can do this in interface builder. Select your button and then tap the "Identity Inspector" (3rd tab on the right). Under "User Defined Runtime Attributes" hit the plus button. and type layer.conerRadius for the key and 1 for the value. This will set the corner radius by KVO. you can now copy this button or duplicate it with alt+drag and the copies will also have the corner radius (note it doesn't show in the preview window, but it will show at run time).
Alternatively in code:
myButtons.forEach { button in
button.layer.cornerRadius = 1
}
If you are using an outlet collection you can do this in a didSet since that will be called when iOS sets the outlet from the nib file/ storyboard.
extension UIView {
func addCornerRadius(_ radius: CGFloat) {
self.layer.cornerRadius = radius
}
func applyBorder(_ width: CGFloat, borderColor: UIColor) {
self.layer.borderWidth = width
self.layer.borderColor = borderColor.cgColor
}
func addShadow(color: UIColor, opacity: Float, offset: CGSize, radius: CGFloat) {
self.layer.shadowColor = color.cgColor
self.layer.shadowOpacity = opacity
self.layer.shadowOffset = offset
self.layer.shadowRadius = radius
}
func displayToast(message: String) {
let style = CSToastStyle(defaultStyle: ())
style?.backgroundColor = UIColor.black
style?.titleColor = UIColor.white
style?.messageColor = UIColor.white
makeToast(message, duration: 3.0, position: CSToastPositionTop, style: style)
} }
Use As Below :
self.view.addCornerRadius(10)
self.view.addShadow(color: .lightGray, opacity: 1.0, offset: CGSize(width: 1, height: 1), radius: 2)