I have a custom inputAccessoryView and am trying to toggle hiding/showing it. I don't want to utilize .isHidden or .removeFromSuperView(), rather, use the bottom slide in/out, which seems to be native as I present other viewControllers onto the hierarchy and this animation executes.
I've tried to resignFirstResponder with no luck and there doesn't seem to be any existing commentary around this. Any thoughts would be appreciated as I am admittedly stumped.
class CustomInputAccessoryView: UIView {
let customTextView: CustomInputTextView = {
let tv = CustomInputTextView()
tv.isScrollEnabled = false
return tv
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.white
autoresizingMask = .flexibleHeight
addSubview(customTextView)
customTextView.topAnchor.constraint(equalTo: topAnchor, constant: 12).isActive = true
customTextView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: 8).isActive = true
customTextView.leftAnchor.constraint(equalTo: leftAnchor, constant: 10).isActive = true
customTextView.rightAnchor.constraint(equalTo: rightAnchor, constant: 10).isActive = true
customTextView.heightAnchor.constraint(greaterThanOrEqualToConstant: frame.height - 20).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var intrinsicContentSize: CGSize {
return .zero
}
}
class CustomInputTextView: UITextView {
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
}
override var canResignFirstResponder: Bool {
return true
}
override var canBecomeFirstResponder: Bool {
return true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init coder has not been implemented")
}
}
//in viewcontroller
lazy var inputContainerView: CustomInputAccessoryView = {
let frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: 60)
let customInputAccessoryView = CustomInputAccessoryView(frame: frame)
return customInputAccessoryView
}()
//onLoad
override var inputAccessoryView: UIView? {
get { return inputContainerView }
}
I don't know if this is really what you're going for, but give it a try.
Tapping anywhere in the view will show/hide the input accessory view:
class TestInputViewController: UIViewController {
//in viewcontroller
lazy var inputContainerView: CustomInputAccessoryView = {
let frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: 60)
let customInputAccessoryView = CustomInputAccessoryView(frame: frame)
return customInputAccessoryView
}()
override var canBecomeFirstResponder: Bool {
return true
}
//onLoad
override var inputAccessoryView: UIView? {
get { return inputContainerView }
}
override func viewDidLoad() {
super.viewDidLoad()
// so we can see the frame
inputContainerView.backgroundColor = .blue
// tap anywhere in view to show / hide input accessory view
let g = UITapGestureRecognizer(target: self, action: #selector(didTap(sender:)))
view.addGestureRecognizer(g)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
#objc func didTap(sender: UITapGestureRecognizer) {
if self.isFirstResponder {
resignFirstResponder()
} else {
becomeFirstResponder()
}
}
}
class CustomInputAccessoryView: UIView {
let customTextView: CustomInputTextView = {
let tv = CustomInputTextView()
tv.isScrollEnabled = false
return tv
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.white
autoresizingMask = .flexibleHeight
addSubview(customTextView)
customTextView.translatesAutoresizingMaskIntoConstraints = false
customTextView.topAnchor.constraint(equalTo: topAnchor, constant: 12).isActive = true
customTextView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -8).isActive = true
customTextView.leftAnchor.constraint(equalTo: leftAnchor, constant: 10).isActive = true
customTextView.rightAnchor.constraint(equalTo: rightAnchor, constant: -10).isActive = true
customTextView.heightAnchor.constraint(greaterThanOrEqualToConstant: frame.height - 20).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var intrinsicContentSize: CGSize {
return .zero
}
}
class CustomInputTextView: UITextView {
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
}
override var canResignFirstResponder: Bool {
return true
}
override var canBecomeFirstResponder: Bool {
return true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init coder has not been implemented")
}
}
Not related to showing / hiding, but a few of your constraints were wrong, causing the text view to be misplaced.
Related
I have the following class that creates a UIButton subclass. I have set this up so the button automatically constrains itself to the superview and sets it's height.
#IBDesignable
class PrimaryButtonConstrained: UIButton {
#IBInspectable
var cornerRadius: CGFloat = 8 {
didSet {
setupPrimaryConstrainedButton()
}
}
#IBInspectable
var borderWidth: CGFloat = 0 {
didSet {
setupPrimaryConstrainedButton()
}
}
#IBInspectable
var topConstraint: CGFloat = 100 {
didSet {
layoutSubviews()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupPrimaryConstrainedButton()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupPrimaryConstrainedButton()
}
override class var requiresConstraintBasedLayout: Bool {
return true
}
override func layoutSubviews() {
super.layoutSubviews()
translatesAutoresizingMaskIntoConstraints = false
leadingAnchor.constraint(equalTo: superview!.layoutMarginsGuide.leadingAnchor).isActive = true
trailingAnchor.constraint(equalTo: superview!.layoutMarginsGuide.trailingAnchor).isActive = true
heightAnchor.constraint(equalToConstant: 50).isActive = true
topAnchor.constraint(equalTo: superview!.topAnchor, constant: topConstraint).isActive = true
}
func setupPrimaryConstrainedButton() {
setTitleColor(.white, for: .normal)
setTitleColor(.gray, for: .disabled)
setTitleColor(.orange, for: .highlighted)
layer.borderWidth = borderWidth
layer.cornerRadius = cornerRadius
if isEnabled {
backgroundColor = .orange
} else {
backgroundColor = .gray
}
}
override public func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setupPrimaryConstrainedButton()
}
}
The button constraints itself correctly in interface builder when you set the button class, however the frame of the button does not update to the buttons new location as shown.
Does anyone have any ideas on how to fix the frame so it still encloses the button?
Who can help me? I create chat controller via UITableViewController, UINavigationController and I use InputAccessoryView. If I swipe screen to left (dismiss this controller) and return swipe to right (cancel dismiss) - table set adjustContentInset bottom to zero and InputAcessoryView close bottom content tableView. This problem created in ViewWillAppear event. My code:
UITableViewController:
// MARK: - Controller data
lazy var inputContainerView = ChatAccessoryView(frame: .zero, buttonSelector: #selector(sendMessage(sender:)), controller: self)
public var ticketID: Int = 0
private var lastMessageID: Int = 0
private var tableSections: [String] = []
private var tableRows: [[SupportTicketMessage]] = []
private var sendingMessage: Bool = false
private var URLTaskGetMessages: URLSessionDataTask?
private var URLTaskSendMessage: URLSessionDataTask?
// MARK: - Controller overrides
override func loadView() {
super.loadView()
tableView.register(ChatHeaderView.self, forHeaderFooterViewReuseIdentifier: "chatHeader")
tableView.register(ChatFromMessageTableViewCell.self, forCellReuseIdentifier: "chatFromMessage")
tableView.register(ChatToMessageTableViewCell.self, forCellReuseIdentifier: "chatToMessage")
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
if let messages = supportClass.getTicketMessages(ticketID) {
tableSections = messages.sections
tableRows = messages.rows
lastMessageID = messages.lastMessage
scrollTableToBottom(false)
} else {
tableView.setLoaderBackground("Загрузка сообщений...")
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
loadMessages()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
URLTaskGetMessages?.cancel()
URLTaskSendMessage?.cancel()
}
override var inputAccessoryView: UIView? {
return inputContainerView
}
override var canBecomeFirstResponder: Bool {
return true
}
ChatAccessoryView:
class ChatAccessoryView: UIView {
// MARK: - Get params to init
let buttonSelector: Selector
let controller: UIViewController
// MARK: - Data
private let textView = ChatTextView()
private let sendButton = LoadingButton(frame: .zero, text: "Отпр.")
private let blurView = UIVisualEffectView(effect: UIBlurEffect(style: .extraLight))
// MARK: - Init
required init(frame: CGRect, buttonSelector: Selector, controller: UIViewController) {
self.buttonSelector = buttonSelector
self.controller = controller
super.init(frame: frame)
configureContents()
blurEffectConfigure()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Overrides
override var intrinsicContentSize: CGSize {
return .zero
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
blurEffectConfigure()
}
override func didMoveToWindow() {
super.didMoveToWindow()
if let window = window {
textView.bottomAnchor.constraint(lessThanOrEqualToSystemSpacingBelow: window.safeAreaLayoutGuide.bottomAnchor, multiplier: 1.0).isActive = true
}
}
// MARK: - Private methods
private func configureContents() {
backgroundColor = .clear
autoresizingMask = .flexibleHeight
textView.placeholder = "Напишите сообщение..."
textView.maxHeight = 160
textView.font = .systemFont(ofSize: 17)
textView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
textView.layer.cornerRadius = 8
textView.textContainerInset = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 4)
sendButton.titleLabel?.font = .boldSystemFont(ofSize: 17)
sendButton.setTitleColor(controller.view.tintColor, for: .normal)
sendButton.addTarget(controller, action: buttonSelector, for: .touchUpInside)
blurView.translatesAutoresizingMaskIntoConstraints = false
textView.translatesAutoresizingMaskIntoConstraints = false
sendButton.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(blurView)
self.addSubview(textView)
self.addSubview(sendButton)
blurView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
blurView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
blurView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
blurView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
textView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 20).isActive = true
textView.topAnchor.constraint(equalTo: self.topAnchor, constant: 8).isActive = true
textView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -8 ).isActive = true
textView.trailingAnchor.constraint(equalTo: sendButton.leadingAnchor, constant: -20).isActive = true
sendButton.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20).isActive = true
sendButton.widthAnchor.constraint(equalToConstant: 48).isActive = true
sendButton.centerYAnchor.constraint(equalTo: textView.centerYAnchor).isActive = true
}
private func blurEffectConfigure() {
if #available(iOS 13.0, *) {
if traitCollection.userInterfaceStyle == .light {
blurView.effect = UIBlurEffect(style: .extraLight)
} else {
blurView.effect = UIBlurEffect(style: .dark)
}
}
}
// MARK: - Public methods
public func successSend() {
textView.text = ""
sendButton.loadingMode(false)
}
}
ChatTextView:
class ChatTextView: UITextView {
// MARK: - Data
var maxHeight: CGFloat = 0.0
public let placeholderTextView: UITextView = {
let textView = UITextView()
textView.translatesAutoresizingMaskIntoConstraints = false
textView.backgroundColor = .clear
textView.isScrollEnabled = false
textView.isUserInteractionEnabled = false
textView.textColor = UIColor.black.withAlphaComponent(0.33)
return textView
}()
var placeholder: String? {
get {
return placeholderTextView.text
}
set {
placeholderTextView.text = newValue
}
}
// MARK: - Init
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
backgroundColor = UIColor.black.withAlphaComponent(0.06)
isScrollEnabled = false
autoresizingMask = [.flexibleWidth, .flexibleHeight]
NotificationCenter.default.addObserver(self, selector: #selector(UITextInputDelegate.textDidChange(_:)), name: UITextView.textDidChangeNotification, object: self)
placeholderTextView.font = font
addSubview(placeholderTextView)
NSLayoutConstraint.activate([
placeholderTextView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8),
placeholderTextView.trailingAnchor.constraint(equalTo: trailingAnchor),
placeholderTextView.topAnchor.constraint(equalTo: topAnchor),
placeholderTextView.bottomAnchor.constraint(equalTo: bottomAnchor),
])
colorThemeConfigure()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Overrides
override var text: String! {
didSet {
invalidateIntrinsicContentSize()
placeholderTextView.isHidden = !text.isEmpty
}
}
override var font: UIFont? {
didSet {
placeholderTextView.font = font
invalidateIntrinsicContentSize()
}
}
override var contentInset: UIEdgeInsets {
didSet {
placeholderTextView.contentInset = contentInset
}
}
override var intrinsicContentSize: CGSize {
var size = super.intrinsicContentSize
if size.height == UIView.noIntrinsicMetric {
layoutManager.glyphRange(for: textContainer)
size.height = layoutManager.usedRect(for: textContainer).height + textContainerInset.top + textContainerInset.bottom
}
if maxHeight > 0.0 && size.height > maxHeight {
size.height = maxHeight
if !isScrollEnabled {
isScrollEnabled = true
}
} else if isScrollEnabled {
isScrollEnabled = false
}
return size
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
colorThemeConfigure()
}
// MARK: - Private methods
private func colorThemeConfigure() {
if #available(iOS 13.0, *) {
if traitCollection.userInterfaceStyle == .light {
backgroundColor = UIColor.black.withAlphaComponent(0.06)
placeholderTextView.textColor = UIColor.black.withAlphaComponent(0.33)
} else {
backgroundColor = UIColor.white.withAlphaComponent(0.08)
placeholderTextView.textColor = UIColor.white.withAlphaComponent(0.33)
}
}
}
// MARK: - Obj C methods
#objc private func textDidChange(_ note: Notification) {
invalidateIntrinsicContentSize()
placeholderTextView.isHidden = !text.isEmpty
}
}
Screenshots error:
TableView loaded (all right)
Swipe screen to left and cancel this action (swipe right)
When I returned to controller I see this Bug (last message go to bottom)
Thanks!
You could try something like this:
UITableViewController:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
updateTableViewInsets()
}
private func updateTableViewInsets() {
let offSet = inputContainerView.frame.height
tableView?.contentInset.bottom = offSet
tableView?.scrollIndicatorInsets.bottom = offSet
}
Thank you all for your attention and trying to help me! I solved the problem as follows:
tableView.contentInsetAdjustmentBehavior = .never
tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 53, right: 0)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
I disable automatic tableView.contentInsetAdjustmentBehavior and use tableView.conentInset. On open/close keyboard I change tableView.contentInset.
This works well for me, and pretty simple solution:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
reloadInputViews()
prepareTableViewForViewPresented()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
prepareTableViewForViewDismissed()
}
private func prepareTableViewForViewPresented() {
self.tableView.contentInset.bottom = 0
}
private func prepareTableViewForViewDismissed() {
self.tableView.contentInset.bottom = view.safeAreaInsets.bottom + (inputAccessoryView?.frame.height ?? 0)
}
Known issues:
- when keyboard is shown, tableview still goes behind keyboard
I want to ask you, is there any way to make a swift button has the design like this when clicked, and this when not. I want to give me some proposal or anything to do it.
Create a Custom UIView Class and copy the below code and try it :)
Don't forget to change a couple of values for customizations
class TestView: UIView {
private let selectorView: UIView = UIView()
private let trackView: UIView = UIView()
private var selectorViewLeadingConstraint: NSLayoutConstraint!
private var selectorViewTrailingConstraint: NSLayoutConstraint!
private var tapGesture: UITapGestureRecognizer!
public var isSelected: Bool = false {
didSet {
self.setIsSelected(isSelected)
}
}
override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
}
private func initialize() {
addTrackView()
addSelectorView()
addTapGestures()
}
private func addTapGestures() {
tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapHandler))
addGestureRecognizer(tapGesture)
}
private func addTrackView() {
addSubview(trackView)
trackView.translatesAutoresizingMaskIntoConstraints = false
trackView.layer.cornerRadius = 5
trackView.backgroundColor = .gray
NSLayoutConstraint.activate([
trackView.leadingAnchor.constraint(equalTo: leadingAnchor),
trackView.trailingAnchor.constraint(equalTo: trailingAnchor),
trackView.heightAnchor.constraint(equalToConstant: 10),
trackView.centerYAnchor.constraint(equalTo: centerYAnchor)
])
}
private func addSelectorView(){
addSubview(selectorView)
selectorView.translatesAutoresizingMaskIntoConstraints = false
selectorView.backgroundColor = .green
selectorViewLeadingConstraint = selectorView.leadingAnchor.constraint(equalTo: leadingAnchor)
selectorViewLeadingConstraint.priority = UILayoutPriority(500)
selectorViewTrailingConstraint = selectorView.trailingAnchor.constraint(equalTo: trailingAnchor)
selectorViewTrailingConstraint.priority = .defaultLow
NSLayoutConstraint.activate([
selectorViewLeadingConstraint,
selectorViewTrailingConstraint,
selectorView.topAnchor.constraint(equalTo: topAnchor),
selectorView.bottomAnchor.constraint(equalTo: bottomAnchor),
selectorView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.5),
])
}
override func layoutSubviews() {
super.layoutSubviews()
selectorView.layer.cornerRadius = selectorView.frame.height/2
}
#objc func tapHandler() {
isSelected = !isSelected
}
private func setIsSelected(_ isSelected: Bool) {
selectorViewTrailingConstraint.priority = isSelected ? .defaultHigh : .defaultLow
UIView.animate(withDuration: 0.3) {
self.layoutIfNeeded()
self.selectorView.backgroundColor = isSelected ? .green : .green
}
}
}
I am trying to set the cornerRadius of a UIButton so that it looks like circular , but I am not able to do it .
Here is the code for what I have tried.
func addColorButton() {
let colorButton : UIButton = {
let cb = UIButton()
cb.translatesAutoresizingMaskIntoConstraints = false
cb.backgroundColor = .black
return cb
}()
view.addSubview(colorButton)
colorButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
colorButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10).isActive = true
colorButton.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5).isActive = true
colorButton.heightAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5).isActive = true
colorButton.layer.cornerRadius = 0.5 * colorButton.bounds.size.width
print(colorButton.bounds.width)
colorButton.clipsToBounds = true
}
override func viewDidLoad() {
super.viewDidLoad()
addColorButton()
}
The buttons bounds/frame won't set until autolayout completed.
You could update,
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// Update corner radius here
colorButton.layer.cornerRadius = 0.5 * colorButton.bounds.size.width
}
I think you need to use masksToBounds for this.
colorButton.layer.masksToBounds = true
You can create simple subclass which can use whenever you need
I just show reference of UIButton with shadow and round corner , You can make it IBInspectable to use these properties from storyboard though .
class UIButtonShadow: UIButton {
var yPos : CGFloat = 2 {
didSet {
addBehavior()
}
}
var radius : CGFloat = 2 {
didSet {
addBehavior()
}
}
var cornerRadius : CGFloat = 18 {
didSet {
addBehavior()
}
}
override var bounds: CGRect {
didSet {
addBehavior()
}
}
override var frame: CGRect{
didSet{
addBehavior()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
addBehavior()
}
override func awakeFromNib() {
super.awakeFromNib()
addBehavior()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func addBehavior() {
self.layer.cornerRadius = cornerRadius
let shadowPath = UIBezierPath(roundedRect:self.bounds,cornerRadius:self.layer.cornerRadius)
self.layer.masksToBounds = false
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOffset = CGSize(width:0.0,height:yPos)
self.layer.shadowOpacity = 0.3
self.layer.shadowPath = shadowPath.cgPath
self.layer.shadowRadius = radius
}
}
I create custom View:
final class Clock: UIView {
lazy var hourArrow: CALayer = {
let layer = CALayer()
layer.contents = #imageLiteral(resourceName: "hourArrow").cgImage
layer.contentsGravity = kCAGravityResizeAspect
return layer
}()
lazy var subLayers = [hourArrow,minuteArrow,centerDial,clockDial,clockArrow,block]
override init(frame: CGRect) {
super.init(frame: frame)
subLayers.forEach { (l) in
layer.addSublayer(l)
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
subLayers.forEach({ l in
let rect = CGRect(x: 0, y: 0, width: frame.width, height: frame.height)
l.frame = rect
})
}
}
and i use it ViewController:
class ClockViewController: UIViewController {
lazy var clock: Clock = {
let clock = Clock(frame: .zero)
clock.translatesAutoresizingMaskIntoConstraints = false
return clock
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(clock)
let safeArea = view.safeAreaLayoutGuide
clock.topAnchor.constraint(equalTo: safeArea.topAnchor, constant: 40).isActive = true
clock.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor, constant: 40).isActive = true
clock.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor, constant: -40).isActive = true
clock.heightAnchor.constraint(equalToConstant: 100).isActive = true
// Do any additional setup after loading the view.
}
}
then i move to this view controller, memory up to 10 MB with this layers.
When i dismiss VC, memory don't release but deinit was called.
(I must remove CALayer variables to post this, all of layers are create like first)
Any Ideas?