I am having notification icon and i want to show that icon with badge while new notifications are available or else i need to show only notification icon.
in my case i need to draw a red badge and add it on existing icon image and pass that to other custom component(own component) which is accepting image
Please Refer screenshot 1 for - screenshot is notifcation icon which i have.
Refer screenshot 2 for expected one when new notification available, i want to draw badge and add it to existing icon.
Thanks in advance
AlertWithBadgeView is a view which has two imageViews. The alertImageView is used for a background image and the badgeImageView is used for badge image.
class AlertWithBadgeView: UIView {
var alertImageView: UIImageView!
var badgeImageView: UIImageView!
required init?(coder: NSCoder) {
fatalError("don't use the view from within storyboards or xibs")
}
override init(frame: CGRect) {
super.init(frame: frame)
createView()
}
private func createView() {
_ = {
let imageView = UIImageView()
addSubview(imageView)
imageView.frame = bounds
imageView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
alertImageView = imageView
}()
_ = {
let imageView = UIImageView()
addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
imageView.widthAnchor.constraint(equalToConstant: 20),
imageView.heightAnchor.constraint(equalToConstant: 20),
imageView.topAnchor.constraint(equalTo: topAnchor),
imageView.trailingAnchor.constraint(equalTo: trailingAnchor),
])
badgeImageView = imageView
}()
}
}
Basic creation and configuration can be done from cellForRow(at:) method:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomCell
let alert = AlertWithBadgeView()
alert.alertImageView.image = .init(systemName: "bell.badge")
alert.badgeImageView.image = .init(systemName: "circle.fill")
cell.addSubview(alert)
alert.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
cell.centerYAnchor.constraint(equalTo: alert.centerYAnchor),
cell.leadingAnchor.constraint(equalTo: alert.leadingAnchor, constant: -20),
alert.widthAnchor.constraint(equalToConstant: 30),
alert.heightAnchor.constraint(equalToConstant: 30),
])
return cell
}
Related
A common pattern in UI is to maximize the size of the view up to some point and after that fill the rest of its superview with the spaces.
When using AutoLayout, it can be achieved easily with width <= X constraint. But when using this with UICollectionView, the scroll area matches the size of UICollectionView, so the sides are unscrollable which is unwanted for me.
So, the only way I found to achieve the behavior is to use the proper layout inside the cells themselves. I consider this as a not very good design decision (especially when you have multiple cells). But are there any alternatives available?
We can accomplish this by subclassing UICollectionView and implementing hitTest(_:with:).
What we'll do is extend the "touch area" wider than the collection view itself:
class ExtendedCollectionView: UICollectionView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.bounds.contains(point) {
// touch is inside self.bounds, so
// send it on to super (i.e. "normal" behavior)
// so we can select a cell on tap
return super.hitTest(point, with: event)
}
if self.bounds.insetBy(dx: -self.frame.origin.x, dy: 0).contains(point) {
// touch is outside self.bounds, but
// it IS inside bounds extended left and right, so
// capture the touch for self
return self
}
// touch was outside self.bounds (and outside our extended bounds), so
// send it on to super (i.e. "normal" behavior)
// so the rest of the view hierarchy (buttons, etc)
// can receive the gesture
return super.hitTest(point, with: event)
}
}
You can now use ExtendedCollectionView just as you would use UICollectionView, except you'll be able to scroll it starting from left or right, outside of its bounds.
Here's a complete example:
class ViewController: UIViewController {
var myData: [String] = []
// we'll use these colors for the cell backgrounds
let colors: [UIColor] = [
.systemRed, .systemGreen, .systemBlue,
.systemPink, .systemYellow, .systemTeal,
]
// our "extended collection view"
var collectionView: ExtendedCollectionView!
let cellSize: CGSize = CGSize(width: 80, height: 100)
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Extended CollectionView"
// always respect the safe area
let g = view.safeAreaLayoutGuide
// let's add a "background" image view, sized to fit the view
// so we can easily see the reults
if let img = UIImage(named: "sampleBKG") {
let v = UIImageView()
v.image = img
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
view.sendSubviewToBack(v)
NSLayoutConstraint.activate([
v.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
v.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
v.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
v.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
])
}
// fill myData array with 20 strings
// of different lengths to show this
// works with dynamic width cells
let strs: [String] = [
"Short",
"Bit Longer",
"Much Longer String",
]
for i in 0..<20 {
myData.append("C: \(i) \(strs[i % strs.count])")
}
// set the flow layout properties
let fl = UICollectionViewFlowLayout()
fl.estimatedItemSize = CGSize(width: 50, height: 100)
fl.scrollDirection = .horizontal
fl.minimumLineSpacing = 8
fl.minimumInteritemSpacing = 8
// create an instance of ExtendedCollectionView
collectionView = ExtendedCollectionView(frame: .zero, collectionViewLayout: fl)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.backgroundColor = .clear
view.addSubview(collectionView)
// let's make the collection view
// 80% of the width of the view's safe area
let cvWidthPercent = 0.8
// let's add a label below our custom view
// the same percentage width, so we can
// easily see the layout
let v = UILabel()
v.backgroundColor = .green
v.translatesAutoresizingMaskIntoConstraints = false
v.textAlignment = .center
v.text = "\(cvWidthPercent * 100)%"
view.addSubview(v)
NSLayoutConstraint.activate([
// let's put our collection view
// 80-pts from the top
collectionView.topAnchor.constraint(equalTo: g.topAnchor, constant: 80.0),
// centered Horizontally
collectionView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
// height equal to cell Height
collectionView.heightAnchor.constraint(equalToConstant: cellSize.height),
// 80% of the width of the safe area
collectionView.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: cvWidthPercent),
// constrain label 8-pts below the collection view
v.topAnchor.constraint(equalTo: collectionView.bottomAnchor, constant: 8.0),
// centered Horizontally
v.centerXAnchor.constraint(equalTo: g.centerXAnchor),
// same percentage width
v.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: cvWidthPercent),
])
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(MyDynamicCVCell.self, forCellWithReuseIdentifier: "cvCell")
}
}
extension ViewController: UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return myData.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cvCell", for: indexPath) as! MyDynamicCVCell
cell.contentView.backgroundColor = colors[indexPath.item % colors.count]
cell.label.text = myData[indexPath.item]
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("Did Select Cell At:", indexPath)
}
}
a simple dynamic-width cell
class MyDynamicCVCell: UICollectionViewCell {
let label: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.textAlignment = .center
v.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
contentView.addSubview(label)
NSLayoutConstraint.activate([
label.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
label.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
label.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
])
}
}
The result looks like this:
When you run it, you'll see that you can scroll horizontally, even if you start dragging from the left or right of the cells.
I am using a UICollectionViewController to display a collection of UICollectionViewCell that a user can swipe between, on ios 13, swift 5, and UIKit. I ultimatelly want to re-draw the cell if the user has changed the orientation. In my controller, I have noticed that this piece of code gets executed on orientation change:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath) as! MyCell
return cell
}
In MyCell, I am using this code to monitor then a layout is re-initialized:
override init(frame: CGRect) {
super.init(frame: frame)
setLayout()
print("### orientation:: \(UIDevice.current.orientation.isLandscape) ")
}
Result: This method is called 2 times. When the app starts, and when I change from portrait to landscape. Any subsequent changes, do not call it. I suspect that this is fine, as maybe both versions are cached ( a landscape and a portrait ). If I add code to the setLayout() method based on the UIDevice.current.orientation.isLandscape flag, nothing changes while it executes. Note: The actual result of UIDevice.current.orientation.isLandscape is correct.
Here is an example of what is in my setLayout() method:
let topImageViewContainer = UIView();
topImageViewContainer.translatesAutoresizingMaskIntoConstraints = false
topImageViewContainer.backgroundColor = .yellow
addSubview(topImageViewContainer)
NSLayoutConstraint.activate([
topImageViewContainer.topAnchor.constraint(equalTo: topAnchor),
topImageViewContainer.leftAnchor.constraint(equalTo: leftAnchor),
topImageViewContainer.rightAnchor.constraint(equalTo: rightAnchor),
topImageViewContainer.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.5)
])
if (UIDevice.current.orientation.isLandscape) {
let imageView = UIImageView(image: UIImage(named: "photo_a"))
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFit
topImageViewContainer.addSubview(imageView)
} else {
let imageView = UIImageView(image: UIImage(named: "photo_b"))
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFit
topImageViewContainer.addSubview(imageView)
}
In the example above, the code with "photo_a" is executing, but the ui is still showing photo_b. I suspect I am doing something wrong, and I need to somehow migrate towards an "Adaptive layout" as mentioned here: Different layouts in portrait and landscape mode . However, I am not sure how I could hook in some "viewWillTransition*" functions into a UICollectionViewCell that has a UIView.
How can I adjust my MyCell class, that implements UICollectionViewCell, to allow the cell to be updated when the user changes from landscape to protrait in a Swift intended way?
Hi try to check if is landscape or portrait in cellForItem, my example:
set colloectionView, flowLayaout and cell id:
class ViewController: UIViewController {
private var collectionView: UICollectionView?
let layout = UICollectionViewFlowLayout()
let cellId = "cellId"
now in viewDidLoad set collection view and layout parameters and the collection constraints:
layout.scrollDirection = .horizontal
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView?.delegate = self
collectionView?.dataSource = self
collectionView?.backgroundColor = .clear
collectionView?.register(BottomCellTableViewCell.self, forCellWithReuseIdentifier: cellId)
collectionView?.contentInset = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10)
collectionView?.showsHorizontalScrollIndicator = false
collectionView?.translatesAutoresizingMaskIntoConstraints = false
guard let collection = collectionView else { return }
view.addSubview(collection)
layout.itemSize = CGSize(width: view.bounds.size.width / 2, height: 200)
collection.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20).isActive = true
collection.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 0).isActive = true
collection.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: 0).isActive = true
collection.heightAnchor.constraint(equalToConstant: 200).isActive = true
after that conform to delegate and datasource in a separate extension (more clean code) and set in it numberOfItems and cellForRow:
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! BottomCellTableViewCell
// check here device orientation
if UIDevice.current.orientation.isLandscape {
let image = UIImage(named: "meB")
cell.imageV.image = image
} else {
let image = UIImage(named: "me")
cell.imageV.image = image
}
// reload the view and the collection
DispatchQueue.main.async {
self.collectionView?.reloadData()
}
return cell
}
}
In collectionViewCell adding a subview directly to the cell, and pinning the image view to the cell, is wrong. You add it to a contentView and pin to it relative constraints:
class BottomCellTableViewCell: UICollectionViewCell {
let imageV: UIImageView = {
let iv = UIImageView()
iv.image = UIImage(named: "me")
iv.contentMode = .scaleAspectFill
iv.clipsToBounds = true
iv.translatesAutoresizingMaskIntoConstraints = false
return iv
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.backgroundColor = .red
contentView.addSubview(imageV)
imageV.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
imageV.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
imageV.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
imageV.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
This is the result:
I am implementing a UIScrollView in a CollectionViewCell. I have a custom view which the scroll view should display, hence I am performing the following program in the CollectionViewCell. I have created everything programmatically and below is my code :
struct ShotsCollections {
let title: String?
}
class ShotsMainView: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
containerScrollView.contentSize.width = frame.width * CGFloat(shotsData.count)
shotsData = [ShotsCollections.init(title: "squad"), ShotsCollections.init(title: "genral")]
var i = 0
for data in shotsData {
let customview = ShotsMediaView(frame: CGRect(x: containerScrollView.frame.width * CGFloat(i), y: 0, width: containerScrollView.frame.width, height: containerScrollView.frame.height))
containerScrollView.addSubview(customview)
i += 1
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var shotsData = [ShotsCollections]()
var containerScrollView: UIScrollView = {
let instance = UIScrollView()
instance.isScrollEnabled = true
instance.bounces = true
instance.backgroundColor = blueColor
return instance
}()
private func setupViews() { //These are constraints by using TinyConstraints
addSubview(containerScrollView)
containerScrollView.topToSuperview()
containerScrollView.bottomToSuperview()
containerScrollView.rightToSuperview()
containerScrollView.leftToSuperview()
}
}
Now the issue is, while the scrollview is displayed, the content in it is not. I on printing the contentSize and frame of the scrollview, it displays 0. But if I check the Debug View Hierarchy, scrollview containes 2 views with specific frames.
I am not sure whats going wrongs. Any help is appreciated.
When you are adding customView in your containerScrollView, you are not setting up the constraints between customView and containerScrollView.
Add those constraints and you will be able to see your customViews given that your customView has some height. Also, when you add more view, you would need to remove the bottom constraint of the last added view and create a bottom constraint to the containerScrollView with the latest added view.
I created a sample app for your use case. I am pasting the code and the resultant screen shot below. Hope this is the functionality you are looking for. I suggest you paste this in a new project and tweak the code until you are satisfied. I have added comments to make it clear.
ViewController
import UIKit
class ViewController: UIViewController {
// Initialize dummy data array with numbers 0 to 9
var data: [Int] = Array(0..<10)
override func loadView() {
super.loadView()
// Add collection view programmatically
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.register(ShotsMainView.self, forCellWithReuseIdentifier: ShotsMainView.identifier)
self.view.addSubview(collectionView)
NSLayoutConstraint.activate([
self.view.topAnchor.constraint(equalTo: collectionView.topAnchor),
self.view.bottomAnchor.constraint(equalTo: collectionView.bottomAnchor),
self.view.leadingAnchor.constraint(equalTo: collectionView.leadingAnchor),
self.view.trailingAnchor.constraint(equalTo: collectionView.trailingAnchor),
])
collectionView.delegate = self
collectionView.dataSource = self
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.backgroundColor = UIColor.white
self.view.addSubview(collectionView)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.view.backgroundColor = UIColor.white
}
}
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ShotsMainView.identifier, for: indexPath) as! ShotsMainView
return cell
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// The cell dimensions are set from here
return CGSize(width: collectionView.frame.size.width, height: 100.0)
}
}
ShotsMainView
This is the collection view cell
import UIKit
class ShotsMainView: UICollectionViewCell {
static var identifier = "Cell"
weak var textLabel: UILabel!
override init(frame: CGRect) {
// Initialize with zero frame
super.init(frame: frame)
// Add the scrollview and the corresponding constraints
let containerScrollView = UIScrollView(frame: .zero)
containerScrollView.isScrollEnabled = true
containerScrollView.bounces = true
containerScrollView.backgroundColor = UIColor.blue
containerScrollView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(containerScrollView)
NSLayoutConstraint.activate([
self.topAnchor.constraint(equalTo: containerScrollView.topAnchor),
self.bottomAnchor.constraint(equalTo: containerScrollView.bottomAnchor),
self.leadingAnchor.constraint(equalTo: containerScrollView.leadingAnchor),
self.trailingAnchor.constraint(equalTo: containerScrollView.trailingAnchor)
])
// Add the stack view that will hold the individual items that
// in each row that need to be scrolled horrizontally
let stackView = UIStackView(frame: .zero)
stackView.distribution = .fill
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .horizontal
containerScrollView.addSubview(stackView)
stackView.backgroundColor = UIColor.magenta
NSLayoutConstraint.activate([
containerScrollView.leadingAnchor.constraint(equalTo: stackView.leadingAnchor),
containerScrollView.trailingAnchor.constraint(equalTo: stackView.trailingAnchor),
containerScrollView.topAnchor.constraint(equalTo: stackView.topAnchor),
containerScrollView.bottomAnchor.constraint(equalTo: stackView.bottomAnchor)
])
// Add individual items (Labels in this case).
for i in 0..<10 {
let label = UILabel(frame: .zero)
label.translatesAutoresizingMaskIntoConstraints = false
stackView.addArrangedSubview(label)
label.text = "\(i)"
label.font = UIFont(name: "System", size: 20.0)
label.textColor = UIColor.white
label.backgroundColor = UIColor.purple
label.layer.masksToBounds = false
label.layer.borderColor = UIColor.white.cgColor
label.layer.borderWidth = 1.0
label.textAlignment = .center
NSLayoutConstraint.activate([
label.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 1.0, constant: 0.0),
label.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 0.2, constant: 0.0)
])
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Screenshot
I am building application where you have custom keyboard.
Inside it's class I have created collection view, here is code:
class KeyboardViewController: UIInputViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, UICollectionViewDelegate {
let stickerImages = [
UIImage(named: "Image-1"),
UIImage(named: "Image-2"),
UIImage(named: "Image-3"),
UIImage(named: "Image-4"),
UIImage(named: "Image-5")
]
#IBOutlet var nextKeyboardButton: UIButton!
#IBOutlet var collectionView: UICollectionView!
override func updateViewConstraints() {
super.updateViewConstraints()
}
override func viewDidLoad() {
super.viewDidLoad()
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.scrollDirection = UICollectionView.ScrollDirection.vertical
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
layout.itemSize = CGSize(width: 50, height: 50)
collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(StickersCell.self, forCellWithReuseIdentifier: StickersCell.reuseIdentifier)
collectionView.backgroundColor = UIColor.white
collectionView.showsHorizontalScrollIndicator = false
collectionView.backgroundColor = UIColor.red
collectionView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(collectionView)
self.nextKeyboardButton = UIButton(type: .system)
self.nextKeyboardButton.setTitle(NSLocalizedString("Next Keyboard", comment: "Title for 'Next Keyboard' button"), for: [])
self.nextKeyboardButton.sizeToFit()
self.nextKeyboardButton.translatesAutoresizingMaskIntoConstraints = false
self.nextKeyboardButton.backgroundColor = UIColor.white
self.nextKeyboardButton.addTarget(self, action: #selector(handleInputModeList(from:with:)), for: .allTouchEvents)
self.view.addSubview(self.nextKeyboardButton)
self.nextKeyboardButton.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
self.nextKeyboardButton.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
self.collectionView.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
self.collectionView.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true
self.collectionView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
self.collectionView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
}
override func textWillChange(_ textInput: UITextInput?) {
// The app is about to change the document's contents. Perform any preparation here.
}
override func textDidChange(_ textInput: UITextInput?) {
// The app has just changed the document's contents, the document context has been updated.
var textColor: UIColor
let proxy = self.textDocumentProxy
if proxy.keyboardAppearance == UIKeyboardAppearance.dark {
textColor = UIColor.white
} else {
textColor = UIColor.black
}
self.nextKeyboardButton.setTitleColor(textColor, for: [])
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return stickerImages.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: StickersCell.reuseIdentifier, for: indexPath) as! StickersCell
cell.setImage(stickerImages[indexPath.item]!)
return cell
}}
And here is my Collection View cell class:
class StickersCell: UICollectionViewCell {
static let reuseIdentifier: String = "StickersCell"
lazy var imageView: UIImageView = {
let imageView = UIImageView(frame: .zero)
imageView.contentMode = .scaleAspectFit
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.clipsToBounds = true
contentView.addSubview(imageView)
imageView.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true
imageView.rightAnchor.constraint(equalTo: contentView.rightAnchor).isActive = true
imageView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
imageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setImage(_ image: UIImage) {
imageView.image = image
}}
This code works just fine when adding collection view to any UIView or UIViewController, but when trying to add it to keyboard it throws such errors:
As far as I understand I have placed constraints wrong, but I don't understand what exactly is wrong, especially when it's working fine in simple views or view controllers.
I have googled allot and couldn't find any solutions...
Also this SO questions didn't help as well:
First question
Second question
Third question
I have also tried moving code that creates collection view into viewDidAppear and viewWillAppear methods but same no luck.
Another strange thing:
If I add simple UIView with same constraints to keyboard - everything is working fine. Problem seems to be specifically with collection view.
So, what I have missed? Would be grateful for any help, since I'm battling with this issue for over a week already...
UPDATE:
After reading Apple dev forums, idea came up to my mind:
I have created UITableView same as UICollectionView before and strangely it works. So there's next question:
Are you able to use UICollectionView as custom keyboard at all?
After battling with this issue for 2 weeks finally found working workaround:
For some reason you can't use UIImageView or MSStickerView inside UICollectionViewCell same as in iMessage Extension, so instead I just added transparent UIButton with UIImage inside this button and it worked!
Still don't know why you can't use images or views and couldn't find any specific info about it, but my solutions works and I hope this will help someone in future.
I'm creating following view (display under In Transit) which are used to maintain my product status. See below image.
I want to create this view in UITableViewCell, I have tried by placing fixed height/width view (Circle View with different color) and horizontal gray line view and it's work fine for fixed spot point. I'm able to create this for fixed view using storyboard.
My Problem is, these are dynamic spot point view. Currently it's 4, but it can be vary based on status available in API response.
Anyone have idea? How to achieve this status spot dynamic view?.
You can achieve your thing using UICollectionView inside UITableViewCell.
First create following design for collection view cell. This collection view added inside table view cell.
CollectionViewCell:
See Constraints:
Regarding spotview and circleview you can recognise by constraints and view. So don't confuse therem otherwise all naming convention are available based on view's priority.
Now you need to take outlet of collection view inside UITableViewCell's subclass whatever you made and collection view cell's subview to UICollectionViewCell's subclass.
UITableViewCell:
class CTrackOrderInTransitTVC: UITableViewCell {
#IBOutlet weak var transitView : UIView!
#IBOutlet weak var cvTransit : UICollectionView!
var arrColors: [UIColor] = [.blue, .yellow, .green, .green]
override func awakeFromNib() {
super.awakeFromNib()
}
}
Now add following code in your collection view cell subclass, It's contains outlets of your subViews of collection view cell:
class CTrackOrderInTransitCVC: UICollectionViewCell {
#IBOutlet weak var leftView : UIView!
#IBOutlet weak var rightView : UIView!
#IBOutlet weak var spotView : UIView!
#IBOutlet weak var circleView : UIView!
}
Thereafter, you have to implemented table view datasource method load your collection view cell inside your table.
See the following code:
extension YourViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
//------------------------------------------------------------------------------
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CTrackOrderInTransitTVC", for: indexPath) as! CTrackOrderInTransitTVC
// Reload collection view to update sub views
cell.cvTransit.reloadData()
return cell
}
}
I hope this will help you.
You can do this with a UIStackView using "spacer" views.
Add a clear UIView between each "dot" view, and constrain the width of each "spacer" view equal to the first "spacer" view.
Add a UIStackView, constrain its width and centerY to the tracking line, and set the properties to:
Axis: Horizontal
Alignment: Fill
Distribution: Fill
Spacing: 0
Your code to add the "dots" will be something like this:
for i in 0..<numberOfDots {
create a dot view
add it to the stackView using .addArrangedSubview()
one fewer spacers than dots (e.g. 4 dots have a spacer between each = 3 spacers), so,
if this is NOT the last dot,
create a spacer view
add it to the stackView
}
Keep track of the spacer views, and set their width constraints each equal to the first spacer view.
Here is some starter code which may help you get going. The comments should make it clear what's being done. Everything is being done in code (no #IBOutlets) so you should be able to run it by adding a view controller in storyboard and assigning its custom class to DotsViewController. It adds the view as a "normal" subview... but of course can also be added as a subview of a cell.
class DotView: UIView {
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = bounds.size.height * 0.5
}
}
class TrackingLineView: UIView {
var theTrackingLine: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
return v
}()
var theStack: UIStackView = {
let v = UIStackView()
v.translatesAutoresizingMaskIntoConstraints = false
v.axis = .horizontal
v.alignment = .fill
v.distribution = .fill
v.spacing = 0
return v
}()
var trackingDot: DotView = {
let v = DotView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = UIColor(red: 0.0, green: 0.5, blue: 1.0, alpha: 1.0)
return v
}()
let dotWidth = CGFloat(6)
let trackingDotWidth = CGFloat(20)
var trackingDotCenterX = NSLayoutConstraint()
var dotViews = [DotView]()
var trackingPosition: Int = 0 {
didSet {
let theDot = dotViews[trackingPosition]
trackingDotCenterX.isActive = false
trackingDotCenterX = trackingDot.centerXAnchor.constraint(equalTo: theDot.centerXAnchor, constant: 0.0)
trackingDotCenterX.isActive = true
}
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() -> Void {
// add the tracking line
addSubview(theTrackingLine)
// add the "big" tracking dot
addSubview(trackingDot)
// add the stack view that will hold the small dots (and spacers)
addSubview(theStack)
// the "big" tracking dot will be positioned behind a small dot, so we need to
// keep a reference to its centerXAnchor constraint
trackingDotCenterX = trackingDot.centerXAnchor.constraint(equalTo: theTrackingLine.centerXAnchor, constant: 0.0)
NSLayoutConstraint.activate([
theTrackingLine.centerXAnchor.constraint(equalTo: centerXAnchor, constant: 0.0),
theTrackingLine.centerYAnchor.constraint(equalTo: centerYAnchor, constant: 0.0),
theTrackingLine.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 1.0, constant: -20.0),
theTrackingLine.heightAnchor.constraint(equalToConstant: 2.0),
theStack.centerXAnchor.constraint(equalTo: theTrackingLine.centerXAnchor, constant: 0.0),
theStack.centerYAnchor.constraint(equalTo: theTrackingLine.centerYAnchor, constant: 0.0),
theStack.widthAnchor.constraint(equalTo: theTrackingLine.widthAnchor, multiplier: 1.0, constant: 0.0),
trackingDotCenterX,
trackingDot.widthAnchor.constraint(equalToConstant: trackingDotWidth),
trackingDot.heightAnchor.constraint(equalTo: trackingDot.widthAnchor, multiplier: 1.0),
trackingDot.centerYAnchor.constraint(equalTo: theTrackingLine.centerYAnchor, constant: 0.0),
])
}
func setDots(with colors: [UIColor]) -> Void {
// remove any previous dots and spacers
// (in case we're changing the number of dots after creating the view)
theStack.arrangedSubviews.forEach {
$0.removeFromSuperview()
}
// reset the array of dot views
// (in case we're changing the number of dots after creating the view)
dotViews = [DotView]()
// we're going to set all spacer views to equal widths, so use
// this var to hold a reference to the first one we create
var firstSpacer: UIView?
colors.forEach {
c in
// create a DotView
let v = DotView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = c
// add to array so we can reference it later
dotViews.append(v)
// add it to the stack view
theStack.addArrangedSubview(v)
// dots are round (equal width to height)
NSLayoutConstraint.activate([
v.widthAnchor.constraint(equalToConstant: dotWidth),
v.heightAnchor.constraint(equalTo: v.widthAnchor, multiplier: 1.0),
])
// we use 1 fewer spacers than dots, so if this is not the last dot
if c != colors.last {
// create a spacer (clear view)
let s = UIView()
s.translatesAutoresizingMaskIntoConstraints = false
s.backgroundColor = .clear
// add it to the stack view
theStack.addArrangedSubview(s)
if firstSpacer == nil {
firstSpacer = s
} else {
// we know it's not nil, but we have to unwrap it anyway
if let fs = firstSpacer {
NSLayoutConstraint.activate([
s.widthAnchor.constraint(equalTo: fs.widthAnchor, multiplier: 1.0),
])
}
}
}
}
}
}
class DotsViewController: UIViewController {
var theButton: UIButton = {
let v = UIButton()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .red
v.setTitle("Move Tracking Dot", for: .normal)
v.setTitleColor(.white, for: .normal)
return v
}()
var theTrackingLineView: TrackingLineView = {
let v = TrackingLineView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .white
return v
}()
var trackingDots: [UIColor] = [
.yellow,
.red,
.orange,
.green,
.purple,
]
var currentTrackingPosition = 0
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(red: 1.0, green: 0.8, blue: 0.5, alpha: 1.0)
view.addSubview(theTrackingLineView)
NSLayoutConstraint.activate([
theTrackingLineView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0.0),
theTrackingLineView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0.0),
theTrackingLineView.heightAnchor.constraint(equalToConstant: 100.0),
theTrackingLineView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.9),
])
theTrackingLineView.setDots(with: trackingDots)
theTrackingLineView.trackingPosition = currentTrackingPosition
// add a button so we can move the tracking dot
view.addSubview(theButton)
NSLayoutConstraint.activate([
theButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 40.0),
theButton.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0.0),
])
theButton.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
}
#objc func buttonTapped(_ sender: Any) -> Void {
// if we're at the last dot, reset to 0
if currentTrackingPosition < trackingDots.count - 1 {
currentTrackingPosition += 1
} else {
currentTrackingPosition = 0
}
theTrackingLineView.trackingPosition = currentTrackingPosition
UIView.animate(withDuration: 0.25, animations: {
self.view.layoutIfNeeded()
})
}
}
The result:
i recommend you use a collection view inside table cell, so that way you can define the position with a simple validation