Hamburger Menu UIPanGestureRecogniser - ios

I am trying to make a hamburger menu for my app programmatically and using no plugins. I am starting with the basics, so just trying to get 2 UIViews working together when the user swipes right. The 2 views are the main view and the hamburger menu.
So far the UIViews load in the correct place and when the user swipes right the sidebar slides along. However, when the user lets go the UIView also slides up to the center of the UIView is pinned to the top of the screen. See images below:
Once it gets lost up there I cannot pull it back down. I can still swipe left and right but the center stays constrained to the top of the screen.
I've looked through my code and cannot see what I am doing wrong here?
Here is my code:
class gestureSwipe: UIViewController, UIGestureRecognizerDelegate {
let screenHeight = UIScreen.main.bounds.height
let screenWidth = UIScreen.main.bounds.width
var trayOriginalCenter: CGPoint!
var sideBarSwipeLeftOffset: CGFloat!
var siderBarSwipeRight: CGPoint!
var sideBarSwipeLeft: CGPoint!
let sideBarUIView: UIView! = {
let sideBarUIView = UIView()
sideBarUIView.backgroundColor = UIColor(red:1.0, green:0.0, blue:0.0, alpha:1.0)
sideBarUIView.translatesAutoresizingMaskIntoConstraints = false
sideBarUIView.isUserInteractionEnabled = true
return sideBarUIView
}()
let mainView: UIView = {
let mainView = UIView()
mainView.backgroundColor = UIColor(red:0.0, green:1.0, blue:0.0, alpha:1.0)
mainView.translatesAutoresizingMaskIntoConstraints = false
mainView.isUserInteractionEnabled = true
return mainView
}()
override func viewDidLoad() {
super.viewDidLoad()
let settingsButton = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(HandleSettings))
navigationItem.leftBarButtonItem = settingsButton
view.backgroundColor = UIColor.white
view.addSubview(mainView)
view.addSubview(sideBarUIView)
let SideBarPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(didPan(sender:)))
let MainViewPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(didPan(sender:)))
mainView.addGestureRecognizer(MainViewPanGestureRecognizer)
sideBarUIView.addGestureRecognizer(SideBarPanGestureRecognizer)
sideBarSwipeLeftOffset = 80
siderBarSwipeRight = sideBarUIView.center
sideBarSwipeLeft = CGPoint(x: sideBarUIView.center.x + sideBarSwipeLeftOffset, y: sideBarUIView.center.y)
setupLayout()
}
#IBAction func didPan(sender: UIPanGestureRecognizer) {
let velocity = sender.velocity(in: view)
let translation = sender.translation(in: view)
if sender.state == .began {
trayOriginalCenter = sideBarUIView.center
} else if sender.state == .changed {
print("Gesture began")
sideBarUIView.center = CGPoint(x: trayOriginalCenter.x + translation.x, y: trayOriginalCenter.y)
} else if sender.state == .ended {
print("Gesture ended")
if velocity.x > 0 {
UIView.animate(withDuration: 0.3) {
self.sideBarUIView.center = self.sideBarSwipeLeft
}
} else {
UIView.animate(withDuration: 0.3) {
self.sideBarUIView.center = self.siderBarSwipeRight
}
}
}
}
#IBAction func HandleSettings(sender : UIButton) {
print ("Show settings")
}
private func setupLayout(){
// CONSTRAINTS
mainView.heightAnchor.constraint(equalToConstant: screenHeight).isActive = true
mainView.widthAnchor.constraint(equalToConstant: screenWidth).isActive = true
sideBarUIView.heightAnchor.constraint(equalToConstant: screenHeight).isActive = true
sideBarUIView.widthAnchor.constraint(equalToConstant: screenWidth).isActive = true
sideBarUIView.rightAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
}
}

So I figured out the problem. In the sender.state == .ended, I was using self.sideBarUIView.center = self.sideBarSwipeLeft which kept pushing the view up to the center of the screen.
Edit:
I decided to make the hamburger menu from scratch. Taking some ideas from this example: https://github.com/iosapptemplates/drawer-menu-swift
Then I finally went and added some nice touches like if the menu isn't swiped over enough it was hidden away again.
Here is my final code if anyone needs it.
class hamburgerMenu: UIViewController, UIGestureRecognizerDelegate {
let screenHeight = UIScreen.main.bounds.height
let screenWidth = UIScreen.main.bounds.width
var sideBarOriginalCenter: CGPoint!
var mainView: UIView! = {
let mainView = UIView()
mainView.backgroundColor = UIColor(red:0.0, green:1.0, blue:0.0, alpha:1.0)
mainView.translatesAutoresizingMaskIntoConstraints = false
return mainView
}()
var overlayView: UIView! = {
let viewBlack = UIView()
viewBlack.backgroundColor = UIColor(red:0.0, green:0.0, blue:0.0, alpha:1.0)
viewBlack.translatesAutoresizingMaskIntoConstraints = false
return viewBlack
}()
var sideBarUIView: UIView! = {
let sideBarUIView = UIView()
sideBarUIView.backgroundColor = UIColor(red:1.0, green:0.0, blue:0.0, alpha:1.0)
sideBarUIView.translatesAutoresizingMaskIntoConstraints = false
return sideBarUIView
}()
override func viewDidLoad() {
super.viewDidLoad()
let settingsButton = UIBarButtonItem(barButtonSystemItem: .search, target: self, action: #selector(HandleSettings))
navigationItem.leftBarButtonItem = settingsButton
view.backgroundColor = UIColor.white
view.addSubview(mainView)
view.addSubview(overlayView)
view.addSubview(sideBarUIView)
overlayView.alpha = 0
let swipeLeftGesture = UIPanGestureRecognizer(target: self, action: #selector(didPan(sender:)))
mainView.addGestureRecognizer(swipeLeftGesture)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapOverlay))
mainView.addGestureRecognizer(tapGesture)
setupLayout()
}
#IBAction func didPan(sender: UIPanGestureRecognizer) {
let translation = sender.translation(in: view)
if sender.state == .began {
sideBarOriginalCenter = sideBarUIView.center
} else if sender.state == .changed {
sideBarUIView.center = CGPoint(x: sideBarOriginalCenter.x + translation.x, y: sideBarUIView.center.y)
} else if sender.state == .ended {
let negHalfScreenWidth = ((self.screenWidth/2) * -1) / 2 // This should make -187.5 on iphoneX
if sideBarUIView.center.x > negHalfScreenWidth {
UIView.animate(withDuration: 0.3) {
if self.sideBarUIView.center.x > negHalfScreenWidth {
let leftSideOfScreen = self.screenWidth - self.screenWidth
self.sideBarUIView.center = CGPoint(x: leftSideOfScreen ,y: self.sideBarUIView.center.y)
}
}
} else {
UIView.animate(withDuration: 0.3) {
let leftSideOfScreen = (self.screenWidth / 2) * -1
self.sideBarUIView.center = CGPoint(x: leftSideOfScreen ,y: self.sideBarUIView.center.y)
}
}
}
}
#IBAction fileprivate func didTapOverlay() {
UIView.animate(withDuration: 0.3, animations: {
let leftSideOfScreen = (self.screenWidth / 2) * -1
self.sideBarUIView.center = CGPoint(x: leftSideOfScreen ,y: self.sideBarUIView.center.y)
}) { (success) in
}
}
#IBAction func HandleSettings(sender : UIButton) {
if (sideBarUIView.center.x == 0) {
UIView.animate(withDuration: 0.3, animations: {
let leftSideOfScreen = (self.screenWidth / 2) * -1
self.sideBarUIView.center = CGPoint(x: leftSideOfScreen ,y: self.sideBarUIView.center.y)
})
} else if (sideBarUIView.center.x < -0.1) {
UIView.animate(withDuration: 0.3, animations: {
let leftSideOfScreen = self.screenWidth - self.screenWidth
self.sideBarUIView.center = CGPoint(x: leftSideOfScreen ,y: self.sideBarUIView.center.y)
})
}
}
private func setupLayout(){
mainView.heightAnchor.constraint(equalToConstant: screenHeight).isActive = true
mainView.widthAnchor.constraint(equalToConstant: screenWidth).isActive = true
sideBarUIView.rightAnchor.constraint(equalTo: view.leftAnchor, constant: 20).isActive = true
sideBarUIView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
sideBarUIView.heightAnchor.constraint(equalToConstant: screenHeight).isActive = true
sideBarUIView.widthAnchor.constraint(equalToConstant: screenWidth).isActive = true
overlayView.heightAnchor.constraint(equalToConstant: screenHeight).isActive = true
overlayView.widthAnchor.constraint(equalToConstant: screenWidth).isActive = true
}
}

Related

How to keep specific area of content view within a visible frame while zooming in scrollview?

I have cursor inside scroll content view. I am maintaining relative cursor size when zoom in but its going out of frame. I don't want to keep in center but I have to make sure it's always visible.
Before Zoom:
After Zoom:
I'm assuming you also don't want the user to be able to scroll the "selected rectangle) out of view...
One approach is to calculate the min and max content offsets in scrollViewDidScroll to make sure the "focus rect" is fully visible:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard let fv = focusView else { return }
// get min and max scroll offsets
let mnx = fv.frame.minX * scrollView.zoomScale
let mny = fv.frame.minY * scrollView.zoomScale
let mxx = (fv.frame.maxX * scrollView.zoomScale) - scrollView.frame.width
let mxy = (fv.frame.maxY * scrollView.zoomScale) - scrollView.frame.height
let newX = max(min(scrollView.contentOffset.x, mnx), mxx)
let newY = max(min(scrollView.contentOffset.y, mny), mxy)
// update scroll offset if needed
scrollView.contentOffset = CGPoint(x: newX, y: newY)
}
Here's a quick example, using 6 subviews. For your "checkerboard grid" you would probably track a "focus rect" instead of a "focus view", but the same principle applies:
class RestrictZoomViewController: UIViewController, UIScrollViewDelegate {
let scrollView: UIScrollView = {
let v = UIScrollView()
v.backgroundColor = .systemYellow
return v
}()
let contentView: UIView = {
let v = UIView()
v.backgroundColor = .systemTeal
return v
}()
var focusView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .lightGray
scrollView.addSubview(contentView)
view.addSubview(scrollView)
[contentView, scrollView].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
}
let safeG = view.safeAreaLayoutGuide
let contentG = scrollView.contentLayoutGuide
let frameG = scrollView.frameLayoutGuide
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: safeG.topAnchor, constant: 20.0),
scrollView.leadingAnchor.constraint(equalTo: safeG.leadingAnchor, constant: 20.0),
scrollView.trailingAnchor.constraint(equalTo: safeG.trailingAnchor, constant: -20.0),
scrollView.bottomAnchor.constraint(equalTo: safeG.bottomAnchor, constant: -20.0),
contentView.topAnchor.constraint(equalTo: contentG.topAnchor),
contentView.leadingAnchor.constraint(equalTo: contentG.leadingAnchor),
contentView.trailingAnchor.constraint(equalTo: contentG.trailingAnchor),
contentView.bottomAnchor.constraint(equalTo: contentG.bottomAnchor),
contentView.widthAnchor.constraint(equalTo: frameG.widthAnchor),
contentView.heightAnchor.constraint(equalTo: frameG.heightAnchor),
])
let colors: [UIColor] = [
.systemRed, .systemGreen, .systemBlue,
.orange, .purple, .brown,
]
colors.forEach { c in
let v = UIView()
v.backgroundColor = c
v.layer.borderColor = UIColor.white.cgColor
let t = UITapGestureRecognizer(target: self, action: #selector(tapHandler(_:)))
v.addGestureRecognizer(t)
contentView.addSubview(v)
}
scrollView.minimumZoomScale = 1.0
scrollView.maximumZoomScale = 5.0
scrollView.bouncesZoom = false
scrollView.delegate = self
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// only want to do this once
if let firstView = contentView.subviews.first,
firstView.frame.width == 0 {
var x:CGFloat = 40
let y: CGFloat = 160
var j = 0
for _ in 0..<(contentView.subviews.count / 2) {
contentView.subviews[j].frame = CGRect(x: x, y: y, width: 60, height: 60)
j += 1
contentView.subviews[j].frame = CGRect(x: x, y: y + 100, width: 60, height: 60)
j += 1
x += 100
}
}
}
#objc func tapHandler(_ g: UITapGestureRecognizer) {
guard let v = g.view else { return }
if let fv = focusView {
fv.layer.borderWidth = 0
}
// "highlight" tapped view
v.layer.borderWidth = 1
// set it as focusView
focusView = v
// adjust scroll offset if new focusView is not fully visible
scrollViewDidScroll(scrollView)
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return contentView
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard let fv = focusView else { return }
// get min and max scroll offsets
let mnx = fv.frame.minX * scrollView.zoomScale
let mny = fv.frame.minY * scrollView.zoomScale
let mxx = (fv.frame.maxX * scrollView.zoomScale) - scrollView.frame.width
let mxy = (fv.frame.maxY * scrollView.zoomScale) - scrollView.frame.height
let newX = max(min(scrollView.contentOffset.x, mnx), mxx)
let newY = max(min(scrollView.contentOffset.y, mny), mxy)
// update scroll offset if needed
scrollView.contentOffset = CGPoint(x: newX, y: newY)
}
}

One of the views goes under the other view when doing transform animation with CATransform3DIdentity, even if I use autoreverses

I have reproduced the animation example so it is possible to just copy paste this to see the effect. What I would like is to do the animation on the redview, but I would want it to continue to appear above the green view after the animation, but it seems to go under it after the animation even if I set autoreverses = true. I tried putting redview.transform = .identity in the completion block but it didn't help.
import UIKit
class AnimationTest: UIViewController {
let greenView: UIView = {
let view = UIView()
view.backgroundColor = .green
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let redView: UIView = {
let view = UIView()
view.backgroundColor = .red
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(greenView)
greenView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 150).isActive = true
greenView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 50).isActive = true
greenView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -50).isActive = true
greenView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -150).isActive = true
view.addSubview(redView)
redView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 100).isActive = true
redView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 40).isActive = true
redView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -40).isActive = true
redView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -100).isActive = true
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap))
redView.addGestureRecognizer(tapGesture)
}
#objc func handleTap(sender: UITapGestureRecognizer) {
var transform = CATransform3DIdentity
let angle: CGFloat = 0.1
transform.m34 = -1.0 / 500.0 // [500]: Smaller -> Closer to the 'camera', more distorted
transform = CATransform3DRotate(transform, angle, 0, 1, 0)
let duration = 0.1
let translationAnimation = CABasicAnimation(keyPath: "transform")
translationAnimation.toValue = transform
translationAnimation.duration = duration
translationAnimation.fillMode = .forwards
translationAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut)
translationAnimation.isRemovedOnCompletion = false
translationAnimation.autoreverses = true
CATransaction.setCompletionBlock {
}
redView.layer.add(translationAnimation, forKey: "translation")
CATransaction.commit()
}
}
EDIT:
Also I have another scenario where I add the view into the keywindow because I want it to appear above tab bars. But now the animation goes into the keywindow. How can I make the same animation without going into the keywindow.
import UIKit
class AnimationTest: UIViewController {
let redView: UIView = {
let view = UIView()
view.backgroundColor = .red
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
}
override func viewWillAppear(_ animated: Bool) {
guard let window = UIApplication.shared.keyWindow else { return }
let y = 16 + 10 + 30 + window.safeAreaInsets.top
let width = UIScreen.main.bounds.width - 8 - 8
let height = UIScreen.main.bounds.height - window.safeAreaInsets.top - window.safeAreaInsets.bottom - 16 - 10 - 30 - 4 - 50
redView.frame = CGRect(x: 8, y: y, width: width, height: height)
window.addSubview(redView)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap))
redView.addGestureRecognizer(tapGesture)
}
#objc func handleTap(sender: UITapGestureRecognizer) {
var transform = CATransform3DIdentity
let angle: CGFloat = 0.1
transform.m34 = -1.0 / 500.0 // [500]: Smaller -> Closer to the 'camera', more distorted
transform = CATransform3DRotate(transform, angle, 0, 1, 0)
let duration = 0.1
let translationAnimation = CABasicAnimation(keyPath: "transform")
translationAnimation.fromValue = CATransform3DIdentity
translationAnimation.toValue = transform
translationAnimation.duration = duration
translationAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut)
translationAnimation.autoreverses = true
CATransaction.setCompletionBlock {
}
redView.layer.add(translationAnimation, forKey: "translation") // Key doesn't matter, just call it translation.
CATransaction.commit()
}
}
You need to:
Set the fromValue to CATransform3DIdentity
Remove translationAnimation.isRemovedOnCompletion = false
I also cleaned up some of your other code. You don't need the setCompletionBlock or CATransaction at all. You also don't need fillMode.
#objc func handleTap(sender: UITapGestureRecognizer) {
var transform = CATransform3DIdentity
let angle: CGFloat = 0.1
transform.m34 = -1.0 / 500.0 // [500]: Smaller -> Closer to the 'camera', more distorted
transform = CATransform3DRotate(transform, angle, 0, 1, 0)
let duration = 0.5
let translationAnimation = CABasicAnimation(keyPath: "transform")
translationAnimation.fromValue = CATransform3DIdentity /// here!
translationAnimation.toValue = transform
translationAnimation.duration = duration
translationAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut)
translationAnimation.autoreverses = true
redView.layer.add(translationAnimation, forKey: "transformKey")
}
Before
After
Edit: Red view sinks under window
You will need to adjust the layer's z position to be higher.
override func viewWillAppear(_ animated: Bool) {
guard let window = UIApplication.shared.keyWindow else { return }
let y = 16 + 10 + 30 + window.safeAreaInsets.top
let width = UIScreen.main.bounds.width - 8 - 8
let height = UIScreen.main.bounds.height - window.safeAreaInsets.top - window.safeAreaInsets.bottom - 16 - 10 - 30 - 4 - 50
redView.frame = CGRect(x: 8, y: y, width: width, height: height)
window.addSubview(redView)
redView.layer.zPosition = 100 /// here!
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap))
redView.addGestureRecognizer(tapGesture)
}
0 (Default)
10
100
Half is completely obscured
Part of the view sinks
Completely fine

Is there a way to add subviews to Xcode without reseting the position of the last view added?

This is my code:
let newView = ImageView()
newView.translatesAutoresizingMaskIntoConstraints = false
newView.backgroundColor = .random()
view.addSubview(newView)
newView.widthAnchor.constraint(equalToConstant: 100).isActive = true
newView.heightAnchor.constraint(equalToConstant: 100).isActive = true
newView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
newView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
let panGR = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
panGR.delegate = self
newView.addGestureRecognizer(panGR)
let pinchGR = UIPinchGestureRecognizer(target: self, action: #selector(handlePinch(pinch:)))
pinchGR.delegate = self
newView.addGestureRecognizer(pinchGR)
let rotateGR = UIRotationGestureRecognizer(target: self, action: #selector(handleRotate(rotate:)))
rotateGR.delegate = self
newView.addGestureRecognizer(rotateGR)
Every time the button is pressed, it runs this code. However, all of the subviews keep reseting their position.
This is the code for 'handlePan':
#objc func handlePan(_ gestureRecognizer : UIPanGestureRecognizer){
if gestureRecognizer.state == .began || gestureRecognizer.state == .changed {
let translation = gestureRecognizer.translation(in: self.view)
gestureRecognizer.view!.center = CGPoint(x: gestureRecognizer.view!.center.x + translation.x, y: gestureRecognizer.view!.center.y + translation.y)
gestureRecognizer.setTranslation(CGPoint.zero, in: self.view)
}
}
Because you set a constraints at custom view creating.
After custom view was added to superview, you change its position by dragging - but you do it just change center property. Ok.
When you did added new custom view to your superview - will be executed layout subviews. And all your subviews positions will be reseted according initial constraints.
You need:
Either do not use constraints in your subviews
Either In your handlePan(_ gestureRecognizer : UIPanGestureRecognizer) func change
centerXAnchor,centerYAnchor constraint values, not center property. Because after any layout subviews, their positions will be set to original.
let newView = UIImageView.init(frame: .init(x: 0, y: 0, width: 100, height: 100))
newView.translatesAutoresizingMaskIntoConstraints = false
newView.isUserInteractionEnabled = true
newView.backgroundColor = UIColor.random
view.addSubview(newView)
// newView.widthAnchor.constraint(equalToConstant: 100).isActive = true
// newView.heightAnchor.constraint(equalToConstant: 100).isActive = true
// newView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
// newView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
let panGR = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
panGR.delegate = self
newView.addGestureRecognizer(panGR)
This code works good.
Version with constraints:
#IBAction func addView() {
let newView = CustomView.init()
newView.translatesAutoresizingMaskIntoConstraints = false
newView.isUserInteractionEnabled = true
newView.backgroundColor = UIColor.random
view.addSubview(newView)
newView.widthAnchor.constraint(equalToConstant: 100).isActive = true
newView.heightAnchor.constraint(equalToConstant: 100).isActive = true
newView.centerXConstraint = newView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
newView.centerYConstraint = newView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
newView.centerXConstraint.isActive = true
newView.centerYConstraint.isActive = true
let panGR = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
panGR.delegate = self
newView.addGestureRecognizer(panGR)
}
#objc func handlePan(_ gestureRecognizer : UIPanGestureRecognizer){
if gestureRecognizer.state == .began || gestureRecognizer.state == .changed,
let view = gestureRecognizer.view as? CustomView,
let centerXConstraint = view.centerXConstraint,
let centerYConstraint = view.centerYConstraint {
let translation = gestureRecognizer.translation(in: self.view)
let x = centerXConstraint.constant + translation.x
let y = centerYConstraint.constant + translation.y
centerXConstraint.constant = x
centerYConstraint.constant = y
gestureRecognizer.setTranslation(CGPoint.zero, in: self.view)
}
}
Add constraint variables in your custom class
class CustomView: UIView {
var centerXConstraint: NSLayoutConstraint!
var centerYConstraint: NSLayoutConstraint!
}

How to achieve dynamic scrollable buttons menu?

I am already created dynamic buttons in scroll view but i need to acheive like this way.
I don't want to change the view controller when I click the buttons only get the button titles on same view controller.I have tried following way to creating buttons in scrollview.
class ViewController: UIViewController {
#IBOutlet weak var productScrollView: UIScrollView!
var buttonValues = ["Equity","Commodity","Derivatives","Market","Products","Values"]
override func viewDidLoad() {
super.viewDidLoad()
let scrollingView = colorButtonsView(buttonSize: CGSize(width:100.0,height:30.0), buttonCount: buttonValues.count)
productScrollView.contentSize = scrollingView.frame.size
productScrollView.addSubview(scrollingView)
productScrollView.showsHorizontalScrollIndicator = false
productScrollView.indicatorStyle = .default
productScrollView.setContentOffset(.zero, animated: false)
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func colorButtonsView(buttonSize:CGSize, buttonCount:Int) -> UIView {
//creates color buttons in a UIView
let buttonView = UIView()
buttonView.backgroundColor = UIColor.white
buttonView.frame.origin = CGPoint(x:0,y:0)
let padding = CGSize(width:10,height:10)
buttonView.frame.size.width = (buttonSize.width + padding.width) * CGFloat(buttonCount)
buttonView.frame.size.height = (buttonSize.height + 2.0 * padding.height)
var buttonPosition = CGPoint(x:padding.width * 0.5,y: padding.height)
let buttonIncrement = buttonSize.width + padding.width
for i in 0...(buttonCount - 1) {
let button = UIButton(type: .custom) as UIButton
button.frame.size = buttonSize
button.frame.origin = buttonPosition
buttonPosition.x = buttonPosition.x + buttonIncrement
button.setTitle(buttonValues[i], for: .normal)
button.setTitleColor(UIColor.black, for: .normal)
button.layer.borderWidth = 1
button.layer.borderColor = UIColor.black.cgColor
button.addTarget(self, action: #selector(colorButtonPressed(sender:)), for: .touchUpInside)
buttonView.addSubview(button)
}
return buttonView
}
#objc func colorButtonPressed(sender:UIButton!){
print(sender.title(for: .normal)!)
sender.setTitleColor(UIColor.blue, for: .normal)
}}
Finaly I achieve in this way
import UIKit
protocol ButtonProtocol{
func selectedButton(withTag : Int)
}
class ButtonsView: UIView {
private var scrollView: UIScrollView?
var buttonProtocolDelegate : ButtonProtocol?
var movingView : UIView?
var buttonWidths = [CGFloat]()
#IBInspectable
var wordsArray: [String] = [String]() {
didSet {
createButtons()
}
}
var padding: CGFloat = 10
var currentWidth: CGFloat = 0
private func createButtons() {
scrollView?.removeFromSuperview()
scrollView = UIScrollView(frame: CGRect(x: 0, y: 0, width: self.frame.size.width+80, height: self.frame.size.height))
self.addSubview(scrollView!)
scrollView!.backgroundColor = UIColor.white
scrollView?.showsHorizontalScrollIndicator = false
scrollView?.showsVerticalScrollIndicator = false
self.calculateButtonWidths()
let totalWidthOfButtons = buttonWidths.reduce(10.0,+)
let isBigButton = buttonWidths.contains(where: {$0 > (scrollView?.frame.size.width)!/2})
for i in 0..<wordsArray.count {
let text = wordsArray[i]
var button = UIButton()
if totalWidthOfButtons >= self.frame.size.width || isBigButton {
button = UIButton(frame: CGRect(x:currentWidth, y: 0.0, width: buttonWidths[i], height: self.frame.size.height))
}else{
button = UIButton(frame: CGRect(x:currentWidth, y: 0.0, width: self.frame.size.width/CGFloat(self.buttonWidths.count), height: self.frame.size.height))
buttonWidths[i] = self.frame.size.width/CGFloat(self.buttonWidths.count)
}
button.tag = i
button.setTitle(text, for: .normal)
button.setTitleColor(UIColor.black, for: .normal)
let buttonWidth = button.frame.size.width
currentWidth = currentWidth + buttonWidth + (i == wordsArray.count - 1 ? 0 : padding)
scrollView!.addSubview(button)
button.addTarget(self, action: #selector(pressed(sender:)), for: .touchUpInside)
}
scrollView!.contentSize = CGSize(width:currentWidth,height:scrollView!.frame.size.height)
self.addMovingView()
}
func addMovingView(){
movingView = UIView.init(frame: CGRect.init(x: 0, y: (scrollView?.frame.size.height)! - 2, width: buttonWidths[0], height: 2))
movingView?.backgroundColor = UIColor.blue
scrollView?.addSubview(movingView!)
}
#objc func pressed(sender : UIButton){
self.buttonProtocolDelegate!.selectedButton(withTag : sender.tag)
self.moveButtonToCenterIfPossible(sender : sender)
}
func animageMovingView(sender : UIButton){
UIView.animate(withDuration: 0.20, delay: 0, options: [UIViewAnimationOptions.curveEaseInOut], animations: {
//Set x position what ever you want, in our case, it will be the beginning of the button
() -> Void in
self.movingView?.frame = CGRect(x: sender.frame.origin.x, y: (self.movingView?.frame.origin.y)! , width: sender.frame.size.width, height: 2)
self.superview?.layoutIfNeeded()
}, completion: { (finished) -> Void in
// ....
})
}
func moveButtonToCenterIfPossible(sender : UIButton){
self.scrollView?.scrollToView(button: sender, animated: true)
// print(sender.frame)
self.animageMovingView(sender : sender)
}
func calculateButtonWidths(){
for i in 0..<wordsArray.count {
let text = wordsArray[i]
let button = UIButton(frame: CGRect(x:0, y: 0.0, width: 100, height: 50))
button.tag = i
button.setTitle(text, for: .normal)
button.sizeToFit()
button.contentEdgeInsets = UIEdgeInsets.init(top: 5, left: padding, bottom: 5, right: padding)
button.sizeToFit()
let buttonWidth = button.frame.size.width
buttonWidths.append(buttonWidth)
}
}
}
extension UIScrollView {
func scrollToView(button:UIButton, animated: Bool) {
if let origin = button.superview {
// let buttonStart = button.frame.origin
let buttonCenterPoint = button.center
var scrollOffset = (origin as? UIScrollView)?.contentOffset
let offset = scrollOffset
let deviceBounds = (origin.superview?.frame.size.width)!/2
// print(buttonStart, deviceBounds, scrollOffset ?? "0.0")
let differenceLeft = buttonCenterPoint.x - (scrollOffset?.x)!
let differenceRight = ((origin as? UIScrollView)?.contentSize.width)! - (contentOffset.x + deviceBounds*2)
if buttonCenterPoint.x > deviceBounds {
// scroll left & right
if differenceLeft > deviceBounds && differenceRight < deviceBounds && differenceRight < button.frame.size.width {
//handle last button
scrollOffset?.x = (offset?.x)! + differenceRight
}else{
//for all others in the middle
scrollOffset?.x = (offset?.x)! + differenceLeft - deviceBounds
}
self.setContentOffset(CGPoint.init(x: (scrollOffset?.x)! , y: 0), animated: true)
// scroll right
}else {
// left most buttons
self.setContentOffset(CGPoint.init(x: 0 , y: 0), animated: true)
}
}
}
}
and your view controller
#IBOutlet weak var buttonsView: ButtonsView!
override func viewDidLoad() {
super.viewDidLoad()
buttonsView.buttonProtocolDelegate = self
buttonsView.wordsArray = ["Offers", "Burgers", "Shakes", "Biryani","Snacks", "Lucknow Biryani","Elepaha","dfasjjlfajd","dafjljafl","546464464"]
} extension ViewController {
func selectedButton(withTag : Int) {
print(buttonsView.wordsArray[withTag])
}}

TextField bottom with iPhone X in Swift

I'm new in Swift, and I have an issue with the iPhone X.
I followed this tutorial: https://www.youtube.com/watch?v=FDay6ocBlnE&index=8&list=PL0dzCUj1L5JEfHqwjBV0XFb9qx9cGXwkq in order to create a Chat App.
My problem is that the textField is fixed to the bottom, and that is not good for the iPhone X.
I really don't know how I can change this, given that I'm more familiar with storyboard and here, the collectionViewController is entirely programmatically. I searched a lot of other tutorials but I found nothing to help.
This is my code:
The bottom view (with the textfield):
class ChatInputContainerView: UIView, UITextFieldDelegate {
// ...
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .red
// ...
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
The CollectionViewController:
class ChatLogController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
// ...
lazy var inputContainerView: ChatInputContainerView = {
// I can't change the y value (it changes nothing)
let chatInputContainerView = ChatInputContainerView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: 54))
chatInputContainerView.chatLogController = self
return chatInputContainerView
}()
override var inputAccessoryView: UIView? {
get {
return inputContainerView
}
}
override var canBecomeFirstResponder : Bool {
return true
}
}
Update
Here's the entire code:
import UIKit
import UserNotifications
class ChatLogController: UICollectionViewController, UITextFieldDelegate, UICollectionViewDelegateFlowLayout, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
var user: Userm? {
didSet {
navigationItem.title = user?.username
loadMessages()
}
}
var messages = [Message]()
func loadMessages() {
guard let toId = user?.id else {
return
}
Api.Message.observeUserDiscussion(toId: toId) { (message) in
self.messages.append(message)
DispatchQueue.main.async(execute: {
self.collectionView?.reloadData()
//scroll to the last index
let indexPath = IndexPath(item: self.messages.count - 1, section: 0)
self.collectionView?.scrollToItem(at: indexPath, at: .bottom, animated: true)
})
}
}
let cellId = "cellId"
override func viewDidLoad() {
super.viewDidLoad()
UNUserNotificationCenter.current().getNotificationSettings { (settings) in
print("Notification settings: \(settings)")
guard settings.authorizationStatus == .authorized else { return }
//Not authorised
UIApplication.shared.registerForRemoteNotifications()
}
navigationItem.backBarButtonItem = UIBarButtonItem(title: " ", style: .plain, target: nil, action: nil)
collectionView?.contentInset = UIEdgeInsets(top: 8, left: 0, bottom: 20, right: 0)
// collectionView?.scrollIndicatorInsets = UIEdgeInsets(top: 0, left: 0, bottom: 50, right: 0)
collectionView?.alwaysBounceVertical = true
collectionView?.backgroundColor = UIColor.white
collectionView?.register(ChatMessageCell.self, forCellWithReuseIdentifier: cellId)
collectionView?.keyboardDismissMode = .interactive
arrowBackButton(greyBack)
let image = UIImage(named: "iconProfilCog")
navigationItem.rightBarButtonItem = UIBarButtonItem(image: image, style: .plain, target: self, action: #selector(handleParamsMessage))
navigationItem.rightBarButtonItem?.tintColor = UIColor(red: 203/255, green: 203/255, blue: 203/255, alpha: 1)
setupKeyboardObservers()
emptyTextField()
}
func emptyTextField() {
self.inputContainerView.inputTextField.text = ""
self.inputContainerView.sendButton.isEnabled = false
self.inputContainerView.sendButton.alpha = 0.8
}
override func viewDidLayoutSubviews() {
inputContainerView.inputTextField.roundCorners([.topLeft,.bottomLeft], radius: 10)
inputContainerView.backgroundSendButtonView.roundCorners([.topRight,.bottomRight], radius: 22)
}
lazy var inputContainerView: ChatInputContainerView = {
let chatInputContainerView = ChatInputContainerView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: 54))
chatInputContainerView.chatLogController = self
return chatInputContainerView
}()
func handleParamsMessage() {
print("params")
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let detailMessage = storyboard.instantiateViewController(withIdentifier: "MessageDetailTableViewController") as! MessageDetailTableViewController
if let user = user {
detailMessage.userId = user.id!
self.navigationController?.pushViewController(detailMessage, animated: true)
}
}
func handleUploadTap() {
let imagePickerController = UIImagePickerController()
imagePickerController.allowsEditing = true
imagePickerController.delegate = self
//imagePickerController.mediaTypes = [kUTTypeImage as String, kUTTypeMovie as String]
present(imagePickerController, animated: true, completion: nil)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
// if let videoUrl = info[UIImagePickerControllerMediaURL] as? URL {
// //we selected a video
// handleVideoSelectedForUrl(videoUrl)
// } else {
// //we selected an image
handleImageSelectedForInfo(info as [String : AnyObject])
// }
dismiss(animated: true, completion: nil)
}
fileprivate func handleImageSelectedForInfo(_ info: [String: AnyObject]) {
var selectedImageFromPicker: UIImage?
if let editedImage = info["UIImagePickerControllerEditedImage"] as? UIImage {
selectedImageFromPicker = editedImage
} else if let originalImage = info["UIImagePickerControllerOriginalImage"] as? UIImage {
selectedImageFromPicker = originalImage
}
if let selectedImage = selectedImageFromPicker {
HelperService.uploadMessagePictureToDatabase(selectedImage, completion: { (imageUrl) in
self.sendMessageWithImageUrl(imageUrl, image: selectedImage)
})
}
}
override var inputAccessoryView: UIView? {
get {
return inputContainerView
}
}
override var canBecomeFirstResponder : Bool {
return true
}
func setupKeyboardObservers() {
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardDidShow), name: NSNotification.Name.UIKeyboardDidShow, object: nil)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
NotificationCenter.default.removeObserver(self)
}
func handleKeyboardDidShow() {
if messages.count > 0 {
let indexPath = IndexPath(item: messages.count - 1, section: 0)
collectionView?.scrollToItem(at: indexPath, at: .top, animated: true)
}
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return messages.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! ChatMessageCell
cell.chatLogController = self
let message = messages[indexPath.item]
cell.textView.text = message.text
setupCell(cell, message: message)
//lets modify the bubbleView's width somehow???
// cell.bubbleWidthAnchor?.constant = estimateFrameForText(message.text!).width + 25
if let text = message.text {
//a text message
cell.bubbleWidthAnchor?.constant = estimateFrameForText(text).width + 25
cell.textView.isHidden = false
} else if message.imageUrl != nil {
//fall in here if its an image message
cell.bubbleWidthAnchor?.constant = 200
cell.textView.isHidden = true
}
return cell
}
fileprivate func setupCell(_ cell: ChatMessageCell, message: Message) {
if let profileImageUrl = self.user?.profileImageUrl {
let photoUrl = URL(string: profileImageUrl)
cell.profileImageView.sd_setImage(with: photoUrl)
}
if message.fromId == Api.User.CURRENT_USER?.uid {
//outgoing blue
cell.bubbleView.backgroundColor = ChatMessageCell.blueColor
cell.textView.textColor = UIColor.white
cell.profileImageView.isHidden = true
cell.tailImageView.isHidden = true
cell.bubbleViewRightAnchor?.isActive = true
cell.bubbleViewLeftAnchor?.isActive = false
} else {
//incoming gray
cell.bubbleView.backgroundColor = UIColor(red: 243/255, green: 243/255, blue: 243/255, alpha: 1)
cell.textView.textColor = UIColor(red: 70/255, green: 70/255, blue: 70/255, alpha: 1)
cell.profileImageView.isHidden = false
cell.tailImageView.isHidden = false
cell.bubbleViewRightAnchor?.isActive = false
cell.bubbleViewLeftAnchor?.isActive = true
}
if let messageImageUrl = message.imageUrl {
let photoUrl = URL(string: messageImageUrl)
cell.messageImageView.sd_setImage(with: photoUrl)
cell.messageImageView.isHidden = false
// cell.bubbleView.backgroundColor = UIColor(red: 243/255, green: 243/255, blue: 243/255, alpha: 1)
} else {
cell.messageImageView.isHidden = true
}
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
collectionView?.collectionViewLayout.invalidateLayout()
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
var height: CGFloat = 80
let message = messages[indexPath.item]
if let text = message.text {
height = estimateFrameForText(text).height + 18
} else if let imageWidth = message.imageWidth?.floatValue, let imageHeight = message.imageHeight?.floatValue {
// h1 / w1 = h2 / w2
// solve for h1
// h1 = h2 / w2 * w1
height = CGFloat(imageHeight / imageWidth * 200)
}
let width = UIScreen.main.bounds.width
return CGSize(width: width, height: height)
}
fileprivate func estimateFrameForText(_ text: String) -> CGRect {
let size = CGSize(width: 200, height: 1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
return NSString(string: text).boundingRect(with: size, options: options, attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 15, weight: .medium)], context: nil)
}
var containerViewBottomAnchor: NSLayoutConstraint?
func handleSend() {
self.inputContainerView.sendButton.isEnabled = false
let properties = ["text": inputContainerView.inputTextField.text!]
sendMessageWithPropertiesFIR(properties as [String : AnyObject])
}
fileprivate func sendMessageWithImageUrl(_ imageUrl: String, image: UIImage) {
let properties: [String: AnyObject] = ["imageUrl": imageUrl as AnyObject, "imageWidth": image.size.width as AnyObject, "imageHeight": image.size.height as AnyObject]
sendMessageWithPropertiesFIR(properties)
}
func sendMessageWithPropertiesFIR(_ properties: [String: AnyObject]) {
print(properties["text"])
var messageText = ""
if properties["text"] != nil {
messageText = properties["text"] as! String
} else {
messageText = "A envoyé une photo"
}
Api.Message.sendMessageWithProperties(toId: user!.id!, properties: properties) {
Api.Message.isUserMuted(userId: self.user!.id!, completion: { (isMuted) in
if !isMuted {
Api.UserToken.observeUserToken(withUser: self.user!.id!, completion: { (token) in
if let token = token {
Api.User.observeCurrentUser(completion: { (user) in
Api.Notification.sendNotifPush(token: token, message: "\(user.username!): \(messageText)")
})
}
})
}
})
self.emptyTextField()
}
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
handleSend()
return true
}
var startingFrame: CGRect?
var blackBackgroundView: UIView?
var startingImageView: UIImageView?
//my custom zooming logic
func performZoomInForStartingImageView(_ startingImageView: UIImageView) {
self.startingImageView = startingImageView
self.startingImageView?.isHidden = true
self.inputContainerView.inputTextField.resignFirstResponder()
startingFrame = startingImageView.superview?.convert(startingImageView.frame, to: nil)
let zoomingImageView = UIImageView(frame: startingFrame!)
zoomingImageView.backgroundColor = UIColor.red
zoomingImageView.image = startingImageView.image
zoomingImageView.isUserInteractionEnabled = true
zoomingImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleZoomOut)))
if let keyWindow = UIApplication.shared.keyWindow {
blackBackgroundView = UIView(frame: keyWindow.frame)
blackBackgroundView?.backgroundColor = UIColor.black
blackBackgroundView?.alpha = 0
keyWindow.addSubview(blackBackgroundView!)
keyWindow.addSubview(zoomingImageView)
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
// self.inputContainerView.inputTextField.resignFirstResponder()
self.blackBackgroundView?.alpha = 1
self.inputContainerView.alpha = 0
// math?
// h2 / w1 = h1 / w1
// h2 = h1 / w1 * w1
let height = self.startingFrame!.height / self.startingFrame!.width * keyWindow.frame.width
zoomingImageView.frame = CGRect(x: 0, y: 0, width: keyWindow.frame.width, height: height)
zoomingImageView.center = keyWindow.center
}, completion: { (completed) in
// do nothing
})
}
}
func handleZoomOut(_ tapGesture: UITapGestureRecognizer) {
if let zoomOutImageView = tapGesture.view {
//need to animate back out to controller
zoomOutImageView.layer.cornerRadius = 8
zoomOutImageView.clipsToBounds = true
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.startingFrame = self.startingImageView?.superview?.convert((self.startingImageView?.frame)!, to: nil)
zoomOutImageView.frame = self.startingFrame!
self.blackBackgroundView?.alpha = 0
self.inputContainerView.alpha = 1
}, completion: { (completed) in
zoomOutImageView.removeFromSuperview()
self.startingImageView?.isHidden = false
})
}
}
}
The View:
import UIKit
class ChatInputContainerView: UIView, UITextFieldDelegate {
weak var chatLogController: ChatLogController? {
didSet {
sendButton.addTarget(chatLogController, action: #selector(ChatLogController.handleSend), for: .touchUpInside)
uploadImageView.addGestureRecognizer(UITapGestureRecognizer(target: chatLogController, action: #selector(ChatLogController.handleUploadTap)))
inputTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
}
}
let inputColor = UIColor(red: 243/255, green: 243/255, blue: 243/255, alpha: 1)
lazy var inputTextField: UITextField = {
let textField = UITextField()
textField.placeholder = "Entrer un message..."
textField.translatesAutoresizingMaskIntoConstraints = false
textField.delegate = self
textField.backgroundColor = inputColor
// textField.roundCorners([.topLeft,.bottomLeft], radius: 10)
textField.clipsToBounds = true
return textField
}()
let uploadImageView: UIImageView = {
let uploadImageView = UIImageView()
uploadImageView.isUserInteractionEnabled = true
uploadImageView.image = UIImage(named: "pinImage")
uploadImageView.translatesAutoresizingMaskIntoConstraints = false
return uploadImageView
}()
lazy var backgroundSendButtonView: UIView = {
let backgroundSendButtonView = UIView()
backgroundSendButtonView.backgroundColor = inputColor
backgroundSendButtonView.translatesAutoresizingMaskIntoConstraints = false
return backgroundSendButtonView
}()
let sendButton = UIButton(type: .system)
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .red
addSubview(uploadImageView)
// sendButton.setTitle("Send", for: UIControlState())
sendButton.setImage(UIImage(named: "planeChat"), for: .normal)
sendButton.backgroundColor = UIColor.white
sendButton.tintColor = UIColor(red: 82/255, green: 121/255, blue: 179/255, alpha: 1)
sendButton.layer.cornerRadius = 20
sendButton.clipsToBounds = true
sendButton.translatesAutoresizingMaskIntoConstraints = false
//what is handleSend?
addSubview(sendButton)
addSubview(self.inputTextField)
//x,y,w,h
// A enlever après
self.inputTextField.leftAnchor.constraint(equalTo: uploadImageView.rightAnchor, constant: 12).isActive = true
//self.inputTextField.leftAnchor.constraint(equalTo: leftAnchor, constant: 8).isActive = true
//self.inputTextField.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
self.inputTextField.topAnchor.constraint(equalTo: topAnchor, constant: 4).isActive = true
self.inputTextField.rightAnchor.constraint(equalTo: sendButton.leftAnchor, constant: -4).isActive = true
self.inputTextField.heightAnchor.constraint(equalToConstant: 48).isActive = true
//x,y,w,h
sendButton.rightAnchor.constraint(equalTo: rightAnchor, constant: -8).isActive = true
sendButton.centerYAnchor.constraint(equalTo: inputTextField.centerYAnchor).isActive = true
sendButton.widthAnchor.constraint(equalToConstant: 38).isActive = true
sendButton.heightAnchor.constraint(equalToConstant: 38).isActive = true
//x,y,w,h
uploadImageView.leftAnchor.constraint(equalTo: leftAnchor, constant: 18).isActive = true
uploadImageView.centerYAnchor.constraint(equalTo: inputTextField.centerYAnchor).isActive = true
uploadImageView.widthAnchor.constraint(equalToConstant: 18).isActive = true
uploadImageView.heightAnchor.constraint(equalToConstant: 20).isActive = true
//l//et backgroundSendButtonView = UIView()
//addSubview(backgroundSendButtonView)
// backgroundSendButtonView.roundCorners([.topRight,.bottomRight], radius: 24)
insertSubview(backgroundSendButtonView, belowSubview: sendButton)
backgroundSendButtonView.rightAnchor.constraint(equalTo: rightAnchor, constant: -4).isActive = true
backgroundSendButtonView.centerYAnchor.constraint(equalTo: inputTextField.centerYAnchor).isActive = true
//backgroundSendButtonView.widthAnchor.constraint(equalToConstant: 30).isActive = true
backgroundSendButtonView.leftAnchor.constraint(equalTo: inputTextField.rightAnchor).isActive = true
backgroundSendButtonView.heightAnchor.constraint(equalTo: inputTextField.heightAnchor).isActive = true
//x,y,w,h
// let separatorLineView = UIView()
// separatorLineView.backgroundColor = UIColor(red: 220/255, green: 220/255, blue: 220/255, alpha: 1)
// separatorLineView.translatesAutoresizingMaskIntoConstraints = false
// addSubview(separatorLineView)
// //x,y,w,h
// separatorLineView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
// separatorLineView.topAnchor.constraint(equalTo: topAnchor).isActive = true
// separatorLineView.widthAnchor.constraint(equalTo: widthAnchor).isActive = true
// separatorLineView.heightAnchor.constraint(equalToConstant: 1).isActive = true
let gradientView = UIView()
let colorTop = UIColor.clear.cgColor
let colorBottom = UIColor(red: 0, green: 0, blue: 0, alpha: 0.05).cgColor
gradientView.translatesAutoresizingMaskIntoConstraints = false
addSubview(gradientView)
//x,y,w,h
gradientView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
gradientView.topAnchor.constraint(equalTo: topAnchor, constant: -25).isActive = true
gradientView.widthAnchor.constraint(equalTo: widthAnchor).isActive = true
gradientView.heightAnchor.constraint(equalToConstant: 25).isActive = true
gradientView.backgroundColor = UIColor.clear
let gradientBackground = CAGradientLayer()
gradientBackground.colors = [ colorTop, colorBottom]
gradientBackground.locations = [0.0, 1.0]
var backgroundLayer = CALayer()
backgroundLayer = gradientBackground
let width = UIScreen.main.bounds.size.width
backgroundLayer.frame = CGRect(x: 0, y: 0, width: width, height: 25)
print(backgroundLayer.frame)
print(gradientView.bounds)
gradientView.layer.insertSublayer(backgroundLayer, at: 0)
}
func setGradient(_ view: UIView, colorTop: CGColor, colorBottom: CGColor) {
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
chatLogController?.handleSend()
return true
}
#objc func textFieldDidChange(_ textField: UITextField) {
if textField == self.inputTextField {
if self.inputTextField.text!.isEmpty {
disableButton()
} else {
// sendButton.setTitleColor(typoGreyButton, for: .normal)
self.sendButton.isEnabled = true
self.sendButton.alpha = 1
}
}
}
func disableButton(){
//sendButton.setTitleColor(smoothGray, for: .normal)
sendButton.isEnabled = false
self.sendButton.alpha = 0.8
}
func emptyTextField() {
self.inputTextField.text = ""
disableButton()
}
// func textViewDidChange(_ textView: UITextView) {
// print(textView)
// if textView == self.inputContainerView.inputTextField {
// if (self.inputContainerView.inputTextField.text?.isEmpty)! {
// disableButton()
// } else {
// // sendButton.setTitleColor(typoGreyButton, for: .normal)
// self.inputContainerView.sendButton.isEnabled = true
// }
// }
//
// }
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
If you just want to anchor to the bottom safe area, you can do that anywhere in your view controller:
if #available(iOS 11.0, *) {
someView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
} else {
someView.bottomAnchor.constraint(equalTo: bottomLayoutGuide.topAnchor).isActive = true
}
If, however, you want to use it as a constant (i.e. subtract the length of the bottom safe area from a value), you need to do that in a later lifecycle method like viewDidLayoutSubviews():
if #available(iOS 11.0, *) {
someView.bottomAnchor.constraint(equalTo: anotherView.bottomAnchor, constant: -view.safeAreaInsets.bottom).isActive = true
} else {
someView.bottomAnchor.constraint(equalTo: anotherView.bottomAnchor, constant: -bottomLayoutGuide.length).isActive = true
}
iOS 11 revamped their safe area API so make sure that you support pre-iOS-11 devices as I did in these examples.
I also just noticed that you've set the view's frame explicitly in its intializer. You typically don't want to set a view's frame like that if you're using auto layout (constraints). Therefore, I would suggest not setting the view's frame like you did and instead using constraints to do it.

Resources