inputAccessoryView, API error? _UIKBCompatInputView? UIViewNoIntrinsicMetric, simple code, can't figure out - ios

Help me in one of the two ways maybe:
How to solve the problem? or
How to understand the error message?
Project summary
So I'm learning about inputAccessoryView by making a tiny project, which has only one UIButton. Tapping the button summons the keyboard with inputAccessoryView which contains 1 UITextField and 1 UIButton. The UITextField in the inputAccessoryView will be the final firstResponder that is responsible for the keyboard with that inputAccessoryView
The error message
API error: <_UIKBCompatInputView: 0x7fcefb418290; frame = (0 0; 0 0); layer = <CALayer: 0x60000295a5e0>> returned 0 width, assuming UIViewNoIntrinsicMetric
The code
is very straightforward as below
The custom UIView is used as inputAccessoryView. It installs 2 UI outlets, and tell responder chain that it canBecomeFirstResponder.
class CustomTextFieldView: UIView {
let doneButton:UIButton = {
let button = UIButton(type: .close)
return button
}()
let textField:UITextField = {
let textField = UITextField()
textField.placeholder = "placeholder"
return textField
}()
required init?(coder: NSCoder) {
super.init(coder: coder)
initSetup()
}
override init(frame:CGRect) {
super.init(frame: frame)
initSetup()
}
convenience init() {
self.init(frame: .zero)
}
func initSetup() {
addSubview(doneButton)
addSubview(textField)
}
func autosizing(to vc: UIViewController) {
frame = CGRect(x: 0, y: 0, width: vc.view.frame.size.width, height: 40)
let totalWidth = frame.size.width - 40
doneButton.frame = CGRect(x: totalWidth * 4 / 5 + 20,
y: 0,
width: totalWidth / 5,
height: frame.size.height)
textField.frame = CGRect(x: 20,
y: 0,
width: totalWidth * 4 / 5,
height: frame.size.height)
}
override var canBecomeFirstResponder: Bool { true }
override var intrinsicContentSize: CGSize {
CGSize(width: 400, height: 40)
} // overriding this variable seems to have no effect.
}
Main VC uses the custom UIView as inputAccessoryView. The UITextField in the inputAccessoryView becomes the real firstResponder in the end, I believe.
class ViewController: UIViewController {
let customView = CustomTextFieldView()
var keyboardShown = false
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
customView.autosizing(to: self)
}
#IBAction func summonKeyboard() {
print("hello")
keyboardShown = true
self.becomeFirstResponder()
customView.textField.becomeFirstResponder()
}
override var canBecomeFirstResponder: Bool { keyboardShown }
override var inputAccessoryView: UIView? {
return customView
}
}
I've seen people on the internet says this error message will go away if I run on a physical phone. I didn't go away when I tried.
I override intrinsicContentSize of the custom view, but it has no effect.
The error message shows twice together when I tap summon.
What "frame" or "layer" does the error message refer to? Does it refer to the custom view's frame and layer?

If we use Debug View Hierarchy we can see that _UIKBCompatInputView is part of the (internal) view hierarchy of the keyboard.
It's not unusual to see constraint errors / warnings with internal views.
Since frame and/or intrinsic content size seem to have no effect, I don't think it can be avoided (nor does it seem to need to be).
As a side note, you can keep the "Done" button round by using auto-layout constraints. Here's an example:
class CustomTextFieldView: UIView {
let textField: UITextField = {
let tf = UITextField()
tf.font = .systemFont(ofSize: 16)
tf.autocorrectionType = .no
tf.returnKeyType = .done
tf.placeholder = "placeholder"
// textField backgroundColor so we can see its frame
tf.backgroundColor = .yellow
return tf
}()
let doneButton:UIButton = {
let button = UIButton(type: .close)
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
autoresizingMask = [.flexibleHeight, .flexibleWidth]
[doneButton, textField].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
addSubview(v)
}
NSLayoutConstraint.activate([
// constrain doneButton
// Trailing: 20-pts from trailing
doneButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20.0),
// Top and Bottom 8-pts from top and bottom
doneButton.topAnchor.constraint(equalTo: topAnchor, constant: 8.0),
doneButton.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8.0),
// Width equal to default height
// this will keep the button round instead of oval
doneButton.widthAnchor.constraint(equalTo: doneButton.heightAnchor),
// constrain textField
// Leading: 20-pts from leading
textField.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20.0),
// Trailing: 8-pts from doneButton leading
textField.trailingAnchor.constraint(equalTo: doneButton.leadingAnchor, constant: -8.0),
// vertically centered
textField.centerYAnchor.constraint(equalTo: centerYAnchor),
])
}
}
class CustomTextFieldViewController: UIViewController {
let customView = CustomTextFieldView()
var keyboardShown = false
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func summonKeyboard() {
print("hello")
keyboardShown = true
self.becomeFirstResponder()
customView.textField.becomeFirstResponder()
}
override var canBecomeFirstResponder: Bool { keyboardShown }
override var inputAccessoryView: UIView? {
return customView
}
}

Related

How to animate a UILabel that resizes in parallel with its container view

I am trying to animate a multi-line label inside a UIView. In the container view, the width of the label is relative to the bounds. When the container view is animated, the label jumps to the final state and then the container resizes. How can I instead animate the right side of the text to be continuously pinned to the right edge of the container view as it grows larger?
class ViewController: UIViewController {
var container: ContainerView = ContainerView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(container)
container.frame = CGRect(x: 0, y: 0, width: 150, height: 150)
container.center = view.center
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
UIView.animate(withDuration: 2, delay: 0, options: .curveEaseInOut) {
self.container.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
self.container.center = self.view.center
self.container.layoutIfNeeded()
}
}
}
}
class ContainerView: UIView {
let label: UILabel = {
let label = UILabel()
label.numberOfLines = 0
label.text = "foo bar foo bar foo bar foo bar foo bar foo bar foo foo bar foo bar foo bar foo bar foo bar foo bar foo"
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .purple
addSubview(label)
}
override func layoutSubviews() {
super.layoutSubviews()
let size = label.sizeThatFits(CGSize(width: self.bounds.width, height: CGFloat.greatestFiniteMagnitude))
label.frame = CGRect(x: 0, y: 0, width: self.bounds.width, height: size.height)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
As you've seen, when we change the width of a label UIKit re-calculates the word wrapping immediately.
When we do something like this:
UIView.animate(withDuration: 2, delay: 0, options: .curveEaseInOut) {
self.container.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
self.container.center = self.view.center
self.container.layoutIfNeeded()
}
UIKit sets the width and then animates it. So, as soon as the animation starts, the word wrapping gets set to the "destination" width.
One way to animate the word wrap changes would be to create an animation loop, using small point-size changes.
That works-ish, with two problems:
Using a UILabel, we get vertical shifting (because the text is vertically centered in a label), and
If we make the incremental size changes small, it's smooth but slow. If we make the incremental changes large, it's quick but "jerky."
To solve the first problem, we can use a UITextView, subclassed to work like a top-aligned UILabel. Here's an example:
class MyTextViewLabel: UITextView {
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() -> Void {
isScrollEnabled = false
isEditable = false
isSelectable = false
textContainerInset = UIEdgeInsets.zero
textContainer.lineFragmentPadding = 0
}
}
Not much we can do about the second problem, other than experiment with the width-increment value.
Here's a complete example to look at and play with (using the above MyTextViewLabel class). Note that I'm also using auto-layout / constraints instead of explicit frames:
class MyContainerView: UIView {
let label: MyTextViewLabel = {
let label = MyTextViewLabel()
label.text = "Let's use some readable text for this example. It will make the wrapping changes look more natural than using a bunch of repeating three-character \"words.\""
// let's set the font to the default UILabel font
label.font = .systemFont(ofSize: 17.0)
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
clipsToBounds = true
backgroundColor = .purple
addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
// let's inset the "label" by 4-points so we can see the purple view frame
label.topAnchor.constraint(equalTo: topAnchor, constant: 4.0),
label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 4.0),
label.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -4.0),
// if we want the bottom text to be "clipped"
// don't set the bottom anchor
//label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -4.0),
])
label.backgroundColor = .yellow
}
}
class LabelWrapAnimVC: UIViewController {
// for this example
let startWidth: CGFloat = 150.0
let targetWidth: CGFloat = 200.0
// number of points to increment in each loop
// play with this value...
// 1-point produces a very smooth result, but the total animation time will be slow
// 5-points seems "reasonable" (looks smoother on device than on simulator)
let loopIncrement: CGFloat = 5.0
// total amount of time for the animation
let loopTotalDuration: TimeInterval = 2.0
// each loop anim duration - will be calculated
var loopDuration: TimeInterval = 0
let container: MyContainerView = MyContainerView()
var cWidth: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(container)
container.translatesAutoresizingMaskIntoConstraints = false
let g = view.safeAreaLayoutGuide
cWidth = container.widthAnchor.constraint(equalToConstant: startWidth)
NSLayoutConstraint.activate([
container.centerXAnchor.constraint(equalTo: g.centerXAnchor),
container.centerYAnchor.constraint(equalTo: g.centerYAnchor),
container.heightAnchor.constraint(equalTo: container.widthAnchor),
cWidth,
])
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
doAnim()
}
func animLoop() {
cWidth.constant += loopIncrement
// in case we go over the target width
cWidth.constant = min(cWidth.constant, targetWidth)
UIView.animate(withDuration: loopDuration, animations: {
self.view.layoutIfNeeded()
}, completion: { _ in
if self.cWidth.constant < self.targetWidth {
self.animLoop()
} else {
// maybe do something when animation is done
}
})
}
func doAnim() {
// reset width to original
cWidth.constant = startWidth
// calculate loop duration based on size difference
let numPoints: CGFloat = targetWidth - startWidth
let numLoops: CGFloat = numPoints / loopIncrement
loopDuration = loopTotalDuration / numLoops
DispatchQueue.main.async {
self.animLoop()
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
doAnim()
}
}
I don't know if this will be suitable for your target usage, but it's at least worth a look.

inputAccessoryView not respecting safeAreaLayoutGuide when keyboard is collapsed

I am trying to get an inputAccessoryView working correctly. Namely, I want to be able to display, in this case, a UIToolbar in two possible states:
Above the keyboard - standard and expected behavior
At the bottom of the screen when the keyboard is dismissed (e.g. command + K in the simulator) - and in such instances, have the bottomAnchor respect the bottom safeAreaLayoutGuide.
I've researched this topic extensively but every suggestion I can find has a bunch of workarounds that don't seem to align with Apple engineering's suggested solution. Based on an openradar ticket, Apple engineering proposed this solution be approached as follows:
It’s your responsibility to respect the input accessory view’s
safeAreaInsets. We designed it this way so developers could provide a
background view (i.e., see Safari’s Find on Page input accessory view)
and lay out the content view with respect to safeAreaInsets. This is
fairly straightforward to accomplish. Have a view hierarchy where you
have a container view and a content view. The container view can have
a background color or a background view that encompasses its entire
bounds, and it lays out it’s content view based on safeAreaInsets. If
you’re using autolayout, this is as simple as setting the content
view’s bottomAnchor to be equal to it’s superview’s
safeAreaLayoutGuide.
The link for the above is: http://www.openradar.me/34411433
I have therefore constructed a simple xCode project (iOS App template) that has the following code:
class ViewController: UIViewController {
var field = UITextField()
var containerView = UIView()
var contentView = UIView()
var toolbar = UIToolbar()
override func viewDidLoad() {
super.viewDidLoad()
// TEXTFIELD
field = UITextField(frame: CGRect(x: 20, y: 100, width: view.frame.size.width, height: 50))
field.placeholder = "Enter name..."
field.backgroundColor = .secondarySystemBackground
field.inputAccessoryView = containerView
view.addSubview(field)
// CONTAINER VIEW
containerView.frame = CGRect(x: 0, y: 0, width: view.frame.size.width, height: 50)
containerView.backgroundColor = .systemYellow
containerView.translatesAutoresizingMaskIntoConstraints = false
// CONTENT VIEW
contentView.frame = CGRect(x: 0, y: 0, width: view.frame.size.width, height: 50)
contentView.backgroundColor = .systemPink
contentView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(contentView)
// TOOLBAR
toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: view.frame.size.width, height: 50))
let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil)
let doneButton = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(didTapDone))
toolbar.setItems([flexibleSpace, doneButton], animated: true)
toolbar.backgroundColor = .systemGreen
toolbar.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(toolbar)
NSLayoutConstraint.activate([
contentView.topAnchor.constraint(equalTo: containerView.topAnchor),
contentView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
contentView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
contentView.bottomAnchor.constraint(equalTo: contentView.superview!.safeAreaLayoutGuide.bottomAnchor),
toolbar.topAnchor.constraint(equalTo: contentView.topAnchor),
toolbar.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
toolbar.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
toolbar.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
])
}
#objc private func didTapDone() {
print("done tapped")
}
}
The result works whilst the keyboard is visible but doesn't once the keyboard is dimissed:
I've played around with the heights of the various views with mixed results and making the container view frame height larger (e.g. 100), does show the toolbar when the keyboard is collapsed, it also makes the toolbar too tall for when the keyboard is visible.
Clearly I'm making some auto layout constraint issues but I can't work out and would appreciate any feedback that provides a working solution aligned with Apple's recommendation.
Thanks in advance.
In my case I use the following approach:
import UIKit
extension UIView {
func setDimensions(height: CGFloat, width: CGFloat) {
translatesAutoresizingMaskIntoConstraints = false
heightAnchor.constraint(equalToConstant: height).isActive = true
widthAnchor.constraint(equalToConstant: width).isActive = true
}
func setHeight(_ height: CGFloat) {
translatesAutoresizingMaskIntoConstraints = false
heightAnchor.constraint(equalToConstant: height).isActive = true
}
}
class CustomTextField: UITextField {
override init(frame: CGRect) {
super.init(frame: frame)
}
convenience init(placeholder: String) {
self.init(frame: .zero)
configureUI(placeholder: placeholder)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configureUI(placeholder: String) {
let spacer = UIView()
spacer.setDimensions(height: 50, width: 12)
leftView = spacer
leftViewMode = .always
borderStyle = .none
textColor = .white
keyboardAppearance = .dark
backgroundColor = UIColor(white: 1, alpha: 0.1)
setHeight(50)
attributedPlaceholder = NSAttributedString(string: placeholder, attributes: [.foregroundColor: UIColor(white: 1, alpha: 0.75)])
}
}
I was able to achieve the effect by wrapping the toolbar (chat input bar in my case) and constraining it top/right/left + bottom to safe area of the wrapper.
I'll leave an approximate recipe below.
In your view controller:
override var inputAccessoryView: UIView? {
keyboardHelper
}
override var canBecomeFirstResponder: Bool {
true
}
lazy var keyboardHelper: InputBarWrapper = {
let wrapper = InputBarWrapper()
let inputBar = InputBar()
helper.addSubview(inputBar)
inputBar.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
inputBar.topAnchor.constraint(equalTo: helper.topAnchor),
inputBar.leftAnchor.constraint(equalTo: helper.leftAnchor),
inputBar.bottomAnchor.constraint(equalTo:
helper.safeAreaLayoutGuide.bottomAnchor),
inputBar.rightAnchor.constraint(equalTo: helper.rightAnchor),
])
return wrapper
}()
Toolbar wrapper subclass:
class InputBarWrapper: UIView {
var desiredHeight: CGFloat = 0 {
didSet { invalidateIntrinsicContentSize() }
}
override var intrinsicContentSize: CGSize {
CGSize(width: 0, height: desiredHeight)
}
required init?(coder aDecoder: NSCoder) {
fatalError()
}
override init(frame: CGRect) {
super.init(frame: frame);
autoresizingMask = .flexibleHeight
backgroundColor = UIColor.systemGreen.withAlphaComponent(0.2)
}
}

how to remove safe area from UIView Programmatically in Swift?

This is a custom view, this view creates a square with a given frame with the background color. I am adding a custom view to a subview, the view appears properly. But I am not able to cover the bottom safe area, anyone can help me to remove the safe area from bottom Programmatically.
class CustomView: UIView {
override var frame: CGRect {
didSet {
setNeedsDisplay()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
self.isOpaque = false
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.isOpaque = false
}
override func draw(_ rect: CGRect) {
UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.7).setFill()
UIRectFill(rect)
let square = UIBezierPath(rect: CGRect(x: 200, y: rect.size.height/2 - 150/2, width: UIScreen.main.bounds.size.width - 8, height: 150))
let dashPattern : [CGFloat] = [10, 4]
square.setLineDash(dashPattern, count: 2, phase: 0)
UIColor.white.setStroke()
square.lineWidth = 5
square.stroke()
}
}
Consider the following example:
class ViewController: UIViewController {
private let myView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
configureCustomView()
}
private func configureCustomView() {
myView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(myView)
myView.backgroundColor = .systemPurple
NSLayoutConstraint.activate([
myView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
myView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
myView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
myView.heightAnchor.constraint(equalToConstant: 200)
])
}
}
Result:
If you don't want to go over the safe area, then you could use myView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor) inside NSLayoutConstraint.activate([...]).
So you actually don't have to remove the SafeArea, because you can simply ignore them if you want so...
In case you want to do this fast. (Not programatically)
Open storyboard.
Select your UIView.
Safe Area should be unselected.

Custom BarButtonItem Item: Add red dot on top right corner, Swift 4 IOS

I want to add/set unread flag as red dot top right corner on UIbarButtonItem, See attached image for this
What should i do to add/set red dot on Bar Button item?
Once user tap on item then i want to remove red dot.
UIButton Subclass
Okay, let's start with creating custom UIButton subclass
class ButtonWithBadge: UIButton {
}
now let's create UIView for repsenting red dot
let badgeView: UIView = {
let view = UIView()
view.layer.cornerRadius = 3
view.backgroundColor = .red
return view
}()
Then override init for this subclass and inside add this badgeView to top right corner of your button: set its constraints (right and top equal to button's anchors and width and height to double of badgeView's cornerRadius value)
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(badgeView)
badgeView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
badgeView.rightAnchor.constraint(equalTo: rightAnchor, constant: 3),
badgeView.topAnchor.constraint(equalTo: topAnchor, constant: 3),
badgeView.heightAnchor.constraint(equalToConstant: badgeView.layer.cornerRadius*2),
badgeView.widthAnchor.constraint(equalToConstant: badgeView.layer.cornerRadius*2)
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
Next create variable represeting current state of button:
var isRead: Bool = false
Now let's create some method which hide or unhide badgeView depending on isRead value
func setBadge() {
badgeView.isHidden = isRead
}
Now we have function, right? So let's call this function at the end of init and in didSet of isRead variable
class ButtonWithProperty: UIButton {
var isRead: Bool = false {
didSet {
setBadge()
}
}
override init(frame: CGRect) {
...
setBadge()
}
Adding To ViewController
First create variables for button and view
lazy var barButton: ButtonWithProperty = {
let button = ButtonWithProperty()
... // set color, title, target, etc.
return button
}()
now for example in viewDidLoad add this barButton to UINavigationBar and position it how you want to:
override func viewDidLoad() {
super.viewDidLoad()
...
guard let navigationBar = self.navigationController?.navigationBar else { return }
navigationBar.addSubview(barButton)
barButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
barButton.rightAnchor.constraint(equalTo: navigationBar.rightAnchor, constant: -20),
barButton.bottomAnchor.constraint(equalTo: navigationBar.bottomAnchor, constant: -6)
])
}
Now when you need to, you can just easily change barButton's isRead variable and red dot disappears or appears
barButton.isRead = true
class ButtonWithProperty: UIButton {
var isRead: Bool = false {
didSet {
setBadge()
}
}
lazy var badgeView: UIView = {
let view = UIView()
view.layer.cornerRadius = 3
view.backgroundColor = .red
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(badgeView)
badgeView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
badgeView.rightAnchor.constraint(equalTo: rightAnchor, constant: 3),
badgeView.topAnchor.constraint(equalTo: topAnchor, constant: 3),
badgeView.heightAnchor.constraint(equalToConstant: badgeView.layer.cornerRadius*2),
badgeView.widthAnchor.constraint(equalToConstant: badgeView.layer.cornerRadius*2)
])
setBadge()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setBadge() {
badgeView.isHidden = isRead
}
}
Inside ViewController:
class ViewController: UIViewController {
lazy var barButton: ButtonWithProperty = {
let button = ButtonWithProperty()
... // color, title, target, etc.
return button
}()
...
override func viewDidLoad() {
super.viewDidLoad()
...
guard let navigationBar = self.navigationController?.navigationBar else { return }
navigationBar.addSubview(barButton)
barButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
barButton.rightAnchor.constraint(equalTo: navigationBar.rightAnchor, constant: -20),
barButton.bottomAnchor.constraint(equalTo: navigationBar.bottomAnchor, constant: -6)
])
}
...
}
I've written a custom bar button class to handle this, where I'm using CAShapeLayer to draw a dot on top of the UIBarButtonItem.
// Custom Bar button
class CustomBarButton: UIBarButtonItem
{
// Unread Mark
private var unreadMark: CAShapeLayer?
// Keep track of unread status
var hasUnread: Bool = false
{
didSet
{
setUnread(hasUnread: hasUnread)
}
}
// Toggles unread status
private func setUnread(hasUnread: Bool)
{
if hasUnread
{
unreadMark = CAShapeLayer();
unreadMark?.path = UIBezierPath(ovalIn: CGRect(x: (self.customView?.frame.width ?? 0) - 10, y: 5, width: 5, height: 5)).cgPath;
unreadMark?.fillColor = UIColor.red.cgColor
self.customView?.layer.addSublayer(unreadMark!)
}
else
{
unreadMark?.removeFromSuperlayer()
}
}
}
There is no layer property available for bar button item, so you need to create your UIBarButtonItem using a custom view:
// Bar button property
var barButton:CustomBarButton!
// Initialisation
button = UIButton(type: .custom)
button.frame = CGRect(x: 0, y: 0, width: 70, height: 40)
button.setTitle("Right", for: .normal)
button.setTitleColor(UIColor.green, for: .normal)
// Bar button
barButton = CustomBarButton(customView: button)
button.addTarget(self, action: #selector(toggleStatus(sender:)), for: UIControl.Event.touchUpInside)
// Flexible space (Optional)
let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
toolBar.items = [flexibleSpace, barButton]
And in the IBAction you can toggle the status using the hasUnread property:
#objc func toggleStatus(sender: AnyObject)
{
barButton.hasUnread = !barButton.hasUnread
}
And it will look like:
let dotView = UIView()
let btnSize =yourBarbutton.frame.size
let dotSize = 8
dotView.backgroundColor = .red //Just change colors
dotView.layer.cornerRadius = CGFloat(dotSize/2)
dotView.layer.frame = CGRect(x: Int(btnSize.width)-dotSize/2 , y:
dotSize, width: dotSize, height: dotSize)
yourBarbutton.addSubview(dotView)

inputAccessoryView with intrinsicContentSize in iphone x not working

please do not down vote will try my best to explain problem.
i have an InputAccessoryView which was working fine before iphone x, in iphone x my inputAssesoryView is showing at very bottom below the layoutguide. i found following solution
inputAccessoryView Iphone X
after following idea from above link i can place my textfield above the layout guid but it become unresponsive. which is happening because there is no frame size define for view of inputAccessoryView.
class ChatInputContainerView: UIView, UITextFieldDelegate {
override var intrinsicContentSize: CGSize {
return CGSize.zero
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
lazy var inputTextField: UITextField = {
let textField = UITextField()
textField.placeholder = "Enter message..."
textField.translatesAutoresizingMaskIntoConstraints = false
textField.delegate = self
return textField
}()
override init(frame: CGRect) {
super.init(frame: frame)
autoresizingMask = .flexibleHeight
self.inputTextField.leftAnchor.constraint(equalTo: rightAnchor, constant: 8).isActive = true
self.inputTextField.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor).isActive = true
self.inputTextField.rightAnchor.constraint(equalTo: leftAnchor).isActive = true
self.inputTextField.heightAnchor.constraint(equalTo: heightAnchor).isActive = true
}
}
following is controller where i am using inputAccessory View
class chatController: UITextFieldDelegate {
override func viewDidLoad() {
super.viewDidLoad()
}
lazy var inputContainerView: ChatInputContainerView = {
// let chatInputContainerView = ChatInputContainerView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: 50))
let chatInputContainerView = ChatInputContainerView()
chatInputContainerView.chatLogController = self
return chatInputContainerView
}()
override var inputAccessoryView: UIView? {
get {
return inputContainerView
}
}
override var canBecomeFirstResponder : Bool {
return true
}
}
previously i was using following code to get size of InputAccessoryView which place the view at the bottom with fix height.
let chatInputContainerView = ChatInputContainerView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: 50))
but now i am using following to set inputAccessoryView which is not working properly, textfield inside view become unresponsive because parent view have no size define.
override var intrinsicContentSize: CGSize {
return CGSize.zero
}
autoresizingMask = .flexibleHeight
in following image you can see all my control are now above the safeAreaLayout but they become unresponsive.
sample image
let me know if you do not understand. please help thank you in advance.
Try adding a top anchor to your 'inputTextField'
self.inputTextField.topAnchor.constraint(equalTo: topAnchor).isActive = true
I might be wrong but without knowing where the top is the 'ChatInputContainerView' will have zero height.

Resources