Custom Capsule Background with UIImageView programmatically not working? - swift

I am returning a single UILabel within a UIImageView, within a Cell for a UICollectionViewController. My didSelectItem and dynamic width or working for the cell.
However I am trying to make the UIImageView a capsule shape, the below code I thought would return a circle but isn't. What I want to make is a capsule background layer for the Text, similar to a button but not a button.
class CategoryView: UIView {
private let capsuleBackground: UIImageView = {
let view = UIImageView()
view.layer.backgroundColor = UIColor.white.cgColor
view.layer.borderWidth = 1
view.layer.borderColor = COLOR_PRIMARY?.cgColor
view.layer.cornerRadius = view.frame.size.width/2
view.translatesAutoresizingMaskIntoConstraints = false
view.clipsToBounds = true
view.isUserInteractionEnabled = false
return view
}()
private let textLabel: CustomUILabel = {
let label = CustomUILabel(title: "Hello World")
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
fileprivate func setup() {
addSubview(capsuleBackground)
capsuleBackground.addSubview(textLabel)
capsuleBackground.centerInSuperview()
textLabel.edges(to: capsuleBackground, insets: TinyEdgeInsets(top: 8, left: 8, bottom: 8, right: 8))
}
}
this is what it looks like

view.layer.cornerRadius = 12
🤦‍♂️

Related

Custom view for NSMenuItem appearing blank

I've been trying to make a custom view for a NSMenuItem with an image on it. The code below overrides the original view, but without the image. The item appears blank and smaller.
This code is only an example!
class CustomItemView: NSView {
private let imageView: NSImageView = NSImageView()
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
// self.imageView.frame = frameRect
self.imageView.image = NSImage.init(named: "example")!
self.addSubview(self.imageView)
self.imageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
self.imageView.centerXAnchor.constraint(equalTo: self.centerXAnchor),
self.imageView.centerYAnchor.constraint(equalTo: self.centerYAnchor),
self.imageView.widthAnchor.constraint(equalToConstant: 100),
self.imageView.heightAnchor.constraint(equalToConstant: 100)
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I then simply add set the view like this
let newItem: NSMenuItem = NSMenuItem()
newItem.view = CustomItemView()
newItem.target = self
self.addItem(newItem)

Swift - get random CGPoints from UIView

I am trying to get random CGPoints along the 'outer contour' of UIView so that I will be able to draw UIBezierPath line over obtained CGPoints. For example, get CGPoints from below red dots which I marked myself by hand. Any idea?
###Edit###
As per Sweeper's advice I am trying to add CAShapeLayer into my custom view but it returns nil when I am printing its path. Please could you point out what I am missing?
class ViewController: UIViewController {
let shapeLayer = CAShapeLayer()
override func viewDidLoad() {
super.viewDidLoad()
let view = BubbleView(frame: CGRect(x: self.view.frame.midX / 2, y: self.view.frame.midY/2, width: 150, height: 100))
view.backgroundColor = .gray
view.layer.cornerRadius = 30
self.view.addSubview(view)
}
}
class BubbleView: UIView {
var pathLayer: CAShapeLayer!
override func layoutSubviews() {
pathLayer = CAShapeLayer()
pathLayer.bounds = frame
self.layer.addSublayer(pathLayer)
print(pathLayer.path) // returns nil
}
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .clear
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

How to create this view with constraints programtically

I feel this is pretty simple to accomplish but I can't seem to figure it out. I'm fairly new to not using the storyboard and trying to learn how to set my constraints programatically for my views. I created the view that I want easily in storyboard but can't seem to get it programatically.
I have my view controller has my parent view, and then I call a container view. I imagine in the container view is where I setup my constraints but I can't get the height of my view to stay the same every-time I change to a different device
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var clariView = ClariContainerView()
view.addSubview(clariView)
}
}
This my view controller and then my ClariContainerView looks like this:
class ClariContainerView: UIView {
lazy var clariQuestionView: UIView = {
let containerView = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 0))
containerView.backgroundColor = .blue
containerView.translatesAutoresizingMaskIntoConstraints = false
return containerView
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
public func setupView() {
addSubview(clariQuestionView)
setupLayout()
}
public func setupLayout() {
NSLayoutConstraint.activate([
clariQuestionView.heightAnchor.constraint(equalToConstant: 169)
])
}
}
What I'm trying to recreate is this:
I need the height of the blue view to always be 169.
Here is how you would do that:
First, you don't need to define a frame for your containerView since the translatesAutoresizingMaskIntoConstraints = falsestatement is specifying that you'll be using auto-layout and therefore the frame will be ignored:
lazy var clariQuestionView: UIView = {
let containerView = UIView()
containerView.backgroundColor = .blue
containerView.translatesAutoresizingMaskIntoConstraints = false
return containerView
}()
And here is how you would define your constraints. You need to set height, but also need to pin the view to the bottom, the leading, and the trailing edges of self.view:
public func setupLayout() {
NSLayoutConstraint.activate([
clariQuestionView.heightAnchor.constraint(equalToConstant: 169),
clariQuestionView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
clariQuestionView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
clariQuestionView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor)
])
}
For such a basic layout you don't really need to add heightAnchor. Here is a simple way to achieve desired behavior + bonus — a code snippet to adjust height according to the device's safeAreaInsets.
class ClariContainerView: UIView {
lazy var clariQuestionView: UIView = {
let desiredContainerHeigh = 169
// If you want, you can use commented code to adjust height according to the device's safe area.
// This might be needed if you want to keep the same height over safe area on all devices.
// let safeAreaAdjustment = UIApplication.shared.keyWindow?.rootViewController?.view.safeAreaInsets.bottom ?? 0
let containerView = UIView(frame: CGRect(x: 0, y: UIScreen.main.bounds.height - 169, width: UIScreen.main.bounds.width, height: 169))
containerView.backgroundColor = .blue
containerView.translatesAutoresizingMaskIntoConstraints = true
return containerView
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
public func setupView() {
addSubview(clariQuestionView)
}
}

Stacking views programmatically in UIStackView

I have spent a long time trying to stack the views that I create programmatically. I looked at examples from Add views in UIStackView programmatically but that didn't work. Listed below is the code, I am calling the setUpListings from the view controller. There are two entries but only one entry is shown.
import UIKit
import SnapKit
class ListingsView : UIView {
var containerView: UIView!
var listingsContainerView: UIStackView!
init() {
super.init(frame: CGRect.zero)
setUpContainerView()
setUpListingsContainer()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func setUpContainerView() {
containerView = UIView()
self.addSubview(containerView)
containerView.snp.makeConstraints { (make) in
make.height.equalTo(self)
make.width.equalTo(self)
containerView.backgroundColor = UIColor.white
}
}
func setUpListingsContainer() {
listingsContainerView = UIStackView()
listingsContainerView.distribution = .equalSpacing
listingsContainerView.alignment = .fill
listingsContainerView.axis = .vertical
listingsContainerView.spacing = 10
listingsContainerView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(listingsContainerView)
listingsContainerView.snp.makeConstraints { (make) in
make.top.equalTo(containerView)
make.left.equalTo(containerView)
make.bottom.equalTo(containerView)
make.right.equalTo(containerView)
}
}
func setUpListings(listings: [Listing]) {
for listing in listings {
let listingEntry = ListingEntry(listingId: listing.id)
listingsContainerView.addArrangedSubview(listingEntry)
}
}
class ListingEntry : UIView {
var listingId: String?
var containerView: UIView!
init(listingId: String) {
super.init(frame: CGRect.zero)
self.listingId = listingId
self.setUpContainerView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func setUpContainerView() {
containerView = UIView()
containerView.backgroundColor = UIColor.gray
self.addSubview(containerView)
containerView.snp.makeConstraints { (make) in
make.width.equalTo(150)
make.height.equalTo(150)
}
}
}
}
The view currently looks like
But the blocks should be stacked.
Couple things...
First, I'd suggest learning how constraints and auto-layout work before using something like SnapKit. It can make some things easier --- but until one has a good understanding of the fundamentals, it's not clear what's doing what.
Second, during development, it helps to give views and subviews contrasting background colors. Makes it much easier to see what's happening to the frames at run-time.
So, if you're going to stick with SnapKit...
Try to keep code "clean." That is, don't put anything inside a snp.makeConstraints block that isn't directly related (such as setting background colors).
In your ListingEntry class, you're adding a subview (containerView) and giving that view a width and height of 150, but you are not constraining it to its superview... which results in a view height of Zero.
Take a look at the modifications I made to your code. I added comments that should make the changes clear:
class MiscViewController: UIViewController {
var listingsView: ListingsView = {
let v = ListingsView()
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(listingsView)
listingsView.backgroundColor = .red
// constrain listingsView to all 4 sides with 40-pt "padding"
listingsView.snp.makeConstraints { (make) in
make.top.bottom.leading.trailing.equalToSuperview().inset(40.0)
}
let listings: [Listing] = [
Listing(id: "A"),
Listing(id: "B"),
Listing(id: "C"),
]
listingsView.setUpListings(listings: listings)
}
}
struct Listing {
var id: String = ""
}
class ListingsView : UIView {
var containerView: UIView!
var listingsContainerView: UIStackView!
init() {
super.init(frame: CGRect.zero)
// probably want to set clipsToBounds so any content doesn't extend outside the frame
clipsToBounds = true
setUpContainerView()
setUpListingsContainer()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func setUpContainerView() {
containerView = UIView()
self.addSubview(containerView)
containerView.backgroundColor = UIColor.green
// constrain containerView to all 4 sides
containerView.snp.makeConstraints { (make) in
make.top.bottom.leading.trailing.equalToSuperview()
}
}
func setUpListingsContainer() {
listingsContainerView = UIStackView()
listingsContainerView.distribution = .equalSpacing
listingsContainerView.alignment = .fill
listingsContainerView.axis = .vertical
listingsContainerView.spacing = 10
containerView.addSubview(listingsContainerView)
// constrain listingsContainerView (a stack view) to all 4 sides
listingsContainerView.snp.makeConstraints { (make) in
make.top.leading.bottom.trailing.equalToSuperview()
}
}
func setUpListings(listings: [Listing]) {
for listing in listings {
let listingEntry = ListingEntry(listingId: listing.id)
listingEntry.backgroundColor = .cyan
listingsContainerView.addArrangedSubview(listingEntry)
}
}
class ListingEntry : UIView {
var listingId: String?
var containerView: UIView!
init(listingId: String) {
super.init(frame: CGRect.zero)
self.listingId = listingId
self.setUpContainerView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func setUpContainerView() {
containerView = UIView()
containerView.backgroundColor = .gray
self.addSubview(containerView)
containerView.snp.makeConstraints { (make) in
// you want the "listing container" to be 150 x 150 pts
make.width.equalTo(150)
make.height.equalTo(150)
// and it needs top and bottom constraints to give self a height value
make.top.bottom.equalToSuperview()
// and it needs an x-position constraint
make.leading.equalToSuperview()
}
}
}
}
I've set the "main" ListingsView background color to red ... you don't see it because its containerView subview is green and fills the view.
Each ListingEntry view has a cyan background color, and its containerView has a gray background color.
The result:
and Debug View Hierarchy:
Last notes...
You set your StackView .distribution = .equalSpacing but you also set .spacing = 10, which doesn't make sense.
If you have more ListingEntry views than will fit vertically, you'll run into problems. I'd expect you'd put that into a scroll view.

Adjusting UILabel height depending on contents?

I have a custom CollectionViewCell class, which contains a label and an image. Basically, the image will be constrained to the left, top and right of the cell. However, I want its height to vary depending on the height of the UILabel, which in turns is going to be dependent on the contents inside it. Below is my attempt at it:
import UIKit
class CustomCollectionViewCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .yellow
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
convenience init(cellTextTile text: String) {
self.init()
}
func setupCustomCellElements(cellImageName image: String, cellTitleTextColour textColour: UIColor, cellTitleTextSize textSize: CGFloat, cellTitleFontType fontType: String, cellTitle title: String) {
let cellImage: UIImageView = {
let imageView = UIImageView()
imageView.backgroundColor = .clear
imageView.image = UIImage(named: image)
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
let cellTitle: UILabel = {
let label = UILabel()
label.textColor = textColour
label.font = UIFont(name: fontType, size: textSize)
label.text = title
label.textAlignment = .center
label.frame.size = CGSize(width: self.frame.size.width, height: CGFloat.greatestFiniteMagnitude)
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.sizeToFit()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
addSubview(cellImage)
addSubview(cellTitle)
NSLayoutConstraint.activate([
cellTitle.bottomAnchor.constraint(equalTo: bottomAnchor),
cellTitle.leftAnchor.constraint(equalTo: leftAnchor),
cellTitle.rightAnchor.constraint(equalTo: rightAnchor),
cellImage.bottomAnchor.constraint(equalTo: cellTitle.topAnchor),
cellImage.topAnchor.constraint(equalTo: topAnchor),
cellImage.leftAnchor.constraint(equalTo: leftAnchor),
cellImage.rightAnchor.constraint(equalTo: rightAnchor)
])
}
}
However, with the above code I am not getting the kind of behaviour I am looking for. I want the height of the UIlabel to change based on its contents. And in turn the height of the image to adjust accordingly?
Regards,
Shadi.
You need a height constraint for the imageView
cellImage.heightAnchor.constraint(equalToConstant: 50), // or any value
Also don't add the properties inside the method setupCustomCellElements make them an instance variables , and add them inside the init function
It's better also to add the views to
contentView.addSubview(cellImage)
contentView.addSubview(cellTitle)
and constrcut the constraints with it