I've noticed some Apps of Apple Inc. implement this type of Pop-Up (see linked image). I tried to find it on the internet but without success.
Probably the name of it will be enough for me to implement it into my Storyboard-App, except I don't find a documentation online. So therefore a little explanation would be helpful.
You create it from yourself programmatically, declare your containerView under your controller class, your descriptionLabel, your ImageView (in my case the button too):
let myButton: UIButton = {
let b = UIButton()
b.backgroundColor = .white
b.setTitle("Tap Me!", for: .normal)
b.setTitleColor(.black, for: .normal)
b.titleLabel?.font = .systemFont(ofSize: 17, weight: .semibold)
b.layer.cornerRadius = 20
b.clipsToBounds = true
b.translatesAutoresizingMaskIntoConstraints = false
return b
}()
let containerView: UIView = {
let v = UIView()
v.backgroundColor = .ultraDark // set your pop up backround color
v.layer.cornerRadius = 18
v.clipsToBounds = true
v.alpha = 0
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
let myImageView: UIImageView = {
let iv = UIImageView()
iv.image = UIImage(systemName: "person.2.fill")?.withRenderingMode(.alwaysTemplate)
iv.tintColor = .gray
iv.contentMode = .scaleAspectFit
iv.translatesAutoresizingMaskIntoConstraints = false
return iv
}()
let descriptionLabel: UILabel = {
let l = UILabel()
l.translatesAutoresizingMaskIntoConstraints = false
return l
}()
Now in viewDidLoad set target and constraints:
view.backgroundColor = .black
myButton.addTarget(self, action: #selector(controlTextInTextfields), for: .touchUpInside)
view.addSubview(myButton)
myButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -30).isActive = true
myButton.heightAnchor.constraint(equalToConstant: 50).isActive = true
myButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 30).isActive = true
myButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20).isActive = true
view.addSubview(containerView)
containerView.heightAnchor.constraint(equalToConstant: 230).isActive = true
containerView.widthAnchor.constraint(equalToConstant: 220).isActive = true
containerView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
containerView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
containerView.addSubview(myImageView)
myImageView.heightAnchor.constraint(equalTo: containerView.heightAnchor, multiplier: 0.8).isActive = true
myImageView.widthAnchor.constraint(equalTo: myImageView.heightAnchor, constant: -20).isActive = true
myImageView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).isActive = true
myImageView.centerYAnchor.constraint(equalTo: containerView.centerYAnchor, constant: -20).isActive = true
containerView.addSubview(descriptionLabel)
descriptionLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true
descriptionLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor).isActive = true
descriptionLabel.topAnchor.constraint(equalTo: myImageView.bottomAnchor, constant: -20).isActive = true
descriptionLabel.heightAnchor.constraint(equalToConstant: 20).isActive = true
Now write the func to show pop up and animate it (I added animation because it's cooler):
func savedPopUpView(myView: UIView, label: UILabel, labelText: String) {
let savedLabel = label
savedLabel.text = labelText
savedLabel.font = UIFont.boldSystemFont(ofSize: 18)
savedLabel.textColor = .gray
savedLabel.numberOfLines = 0
savedLabel.textAlignment = .center
myAnimation(myV: myView)
}
fileprivate func myAnimation(myV: UIView) {
myV.alpha = 1
DispatchQueue.main.async {
myV.layer.transform = CATransform3DMakeScale(0, 0, 0)
UIView.animate(withDuration: 0.2, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
myV.layer.transform = CATransform3DMakeScale(1, 1, 1)
}, completion: { (completed) in
//completed
UIView.animate(withDuration: 0.2, delay: 2, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
myV.layer.transform = CATransform3DMakeScale(0.1, 0.1, 0.1)
myV.alpha = 0
})
})
}
}
How to use, call target button func like this:
#objc func controlTextInTextfields() {
savedPopUpView(myView: containerView, label: descriptionLabel, labelText: "Tester: in entfernt")
}
In this case the pop up go away after 2 seconds, you can change this parameter depending on how you like best...
The result:
Related
I have spent a while trying to figure this out, but I just can't. There is probably something really simple I am missing here. I am trying to animate a view coming in from the bottom. This si the code I am using for the view:
private let undoView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .white
view.layer.cornerRadius = 15
view.layer.shadowOffset = .zero
view.layer.shadowOpacity = 0.3
view.layer.shadowRadius = 5
view.layer.shouldRasterize = true
view.layer.rasterizationScale = UIScreen.main.scale
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Undo", for: .normal)
button.titleLabel?.textAlignment = .center
button.setTitleColor(.systemBlue, for: .normal)
let buttonConstraints = [
button.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
button.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
button.topAnchor.constraint(equalTo: view.topAnchor, constant: 16),
button.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -16)
]
view.addSubview(button)
NSLayoutConstraint.activate(buttonConstraints)
let swipeGesture = UISwipeGestureRecognizer(target: view, action: #selector(undoViewSwiped))
view.addGestureRecognizer(swipeGesture)
return view
}()
And this is the code I am using to try and achieve the animation:
func addUndoView() {
var bottomConstraint = undoView.topAnchor.constraint(equalTo: view.bottomAnchor, constant: 0)
let constraints = [
undoView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
undoView.widthAnchor.constraint(equalToConstant: view.bounds.width / 3),
bottomConstraint,
undoView.heightAnchor.constraint(equalToConstant: 50)
]
view.addSubview(undoView)
NSLayoutConstraint.activate(constraints)
undoView.layoutIfNeeded()
bottomConstraint.isActive = false
bottomConstraint = undoView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor, constant: -58)
bottomConstraint.isActive = true
UIView.animate(withDuration: 0.5, delay: 0.2, animations: {
self.undoView.layoutIfNeeded()
})
}
I want the view to just be created underneath the visible view, and then slide up to 8 above the bottom layout margin. This code however just makes the view 'expand' at its final position instead of coming into view from the bottom. Using self.view.layoutIfNeeded() makes the view fly in from the top left of the screen, for some reason.
good day I usually work with transform property for move one view from x position to y portion, please check this example.
class ViewController: UIViewController {
let button : UIButton = {
let button = UIButton(type: .custom)
button.setTitle("Button", for: .normal)
button.backgroundColor = .gray
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
let customView : UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .red
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
view.addSubview(button)
view.addSubview(customView)
NSLayoutConstraint.activate([
button.centerYAnchor.constraint(equalTo: self.view.centerYAnchor),
button.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
customView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
customView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
customView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
customView.heightAnchor.constraint(equalToConstant: 100)
])
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
setupAnimationCustomView()
}
private func setupAnimationCustomView(){
UIView.animate(withDuration: 1, delay: 0 , options: .curveEaseOut, animations: {
print(self.button.frame.origin.y)
let translationY = self.view.frame.height - self.button.frame.origin.y - self.button.frame.height - self.customView.frame.height - 8
self.customView.transform = CGAffineTransform(translationX: 0, y: translationY * (-1))
}, completion: nil)
}
}
on the animation I calculate the distance that I need to move my customView.
Here is the screenshot if the errorI am building the ui of the app programatically and when i run the app it runs perfectly but when i turn the simulation in landscape mode the console shows some layouts errors. Is it okay to just tick the orientation to portrait only in the general app settings because my app is running perfectly in portrait mode and i don't want to run the app in any other orientations or am i making some problems in constraints? Here is my code for the ui...
`let top = UIColor(red: 217/255, green: 30/255, blue: 133/255, alpha: 1)
let bottom = UIColor(red: 242/255, green: 56/255, blue: 15/255, alpha: 1)
let colorOne = UIColor(red: 38/255, green: 38/255, blue: 38/255, alpha: 1)
let colorTwo = UIColor(red: 0/255, green: 0/255, blue: 0/255, alpha: 1)
let colorTop = UIColor.red
let colorBottom = UIColor.green
let topContainer: UIView = {
let top = UIView()
top.translatesAutoresizingMaskIntoConstraints = false
top.backgroundColor = .black
return top
}()
let model: UIImageView = {
let mymodel = UIImageView(image: #imageLiteral(resourceName: "bodybuilder"))
mymodel.translatesAutoresizingMaskIntoConstraints = false
mymodel.contentMode = .scaleToFill
return mymodel
}()
let logo: UIImageView = {
let gymble = UIImageView(image: #imageLiteral(resourceName: "Gymble"))
gymble.contentMode = .scaleAspectFill
gymble.translatesAutoresizingMaskIntoConstraints = false
return gymble
}()
let bottomContainer: UIView = {
let bottom = UIView()
bottom.translatesAutoresizingMaskIntoConstraints = false
return bottom
}()
let pinField: UITextField = {
let phone = UITextField()
phone.backgroundColor = .white
phone.text = "+91"
phone.textAlignment = .center
phone.layer.cornerRadius = 5
phone.font = UIFont(name: "Roboto-Regular", size: 20)
phone.keyboardType = UIKeyboardType.numberPad
phone.translatesAutoresizingMaskIntoConstraints = false
return phone
}()
let phoneFeild: UITextField = {
let phone = UITextField()
phone.backgroundColor = .white
phone.placeholder = "Phone number"
phone.layer.cornerRadius = 5
phone.font = UIFont(name: "Roboto-Regular", size: 20)
phone.keyboardType = UIKeyboardType.phonePad
phone.keyboardAppearance = UIKeyboardAppearance.dark
phone.translatesAutoresizingMaskIntoConstraints = false
return phone
}()
let loginButton: UIButton = {
let button = UIButton(type: .system)
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Login", for: .normal)
button.setTitleColor(.white, for: .normal)
button.layer.cornerRadius = 5
button.layer.masksToBounds = true
button.titleLabel?.font = UIFont(name: "Roboto-Medium", size: 22)
button.addTarget(self, action: #selector(someButtonAction), for: .touchUpInside)
return button
}()
let laterButton: UIButton = {
let button = UIButton(type: .system)
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("I'll login later", for: .normal)
button.setTitleColor(.gray, for: .normal)
button.titleLabel?.font = UIFont(name: "Roboto-Light", size: 16)
return button
}()
let horizontalStack: UIStackView = {
let mystack = UIStackView()
mystack.alignment = UIStackView.Alignment.center
mystack.axis = NSLayoutConstraint.Axis.horizontal
mystack.translatesAutoresizingMaskIntoConstraints = false
mystack.distribution = .fill
mystack.spacing = 15
return mystack
}()
let stack: UIStackView = {
let mystack = UIStackView()
mystack.alignment = UIStackView.Alignment.center
mystack.axis = NSLayoutConstraint.Axis.vertical
mystack.translatesAutoresizingMaskIntoConstraints = false
mystack.distribution = .equalSpacing
return mystack
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .black
view.addSubview(topContainer)
topContainerLayout()
topContainer.addSubview(model)
modelLayout()
view.addSubview(bottomContainer)
bottomContainerLayout()
bottomContainer.addSubview(stack)
stackLayout()
stack.addArrangedSubview(logo)
logoLayout()
stack.addArrangedSubview(horizontalStack)
horizontalStackLayout()
horizontalStack.addArrangedSubview(pinField)
pinFieldLayout()
let padding = UIView(frame: CGRect(x: 0, y: 0, width: 15, height: self.phoneFeild.frame.height))
phoneFeild.leftView = padding
phoneFeild.leftViewMode = UITextField.ViewMode.always
horizontalStack.addArrangedSubview(phoneFeild)
phoneFieldLayout()
let gradientWidth = (UIScreen.main.bounds.width - 32)
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [top.cgColor, bottom.cgColor]
gradientLayer.locations = [0.15, 1]
gradientLayer.startPoint = CGPoint(x: 0, y: 0)
gradientLayer.endPoint = CGPoint(x: 1, y: 0)
gradientLayer.frame = CGRect(x: 0, y: 0, width: gradientWidth, height: 50)
gradientLayer.cornerRadius = 5
loginButton.layer.insertSublayer(gradientLayer, at: 0)
stack.addArrangedSubview(loginButton)
loginButtonLayout()
stack.addArrangedSubview(laterButton)
LaterButtonLayout()
}
func LaterButtonLayout(){
laterButton.heightAnchor.constraint(equalToConstant: 30).isActive = true
}
func loginButtonLayout(){
loginButton.widthAnchor.constraint(equalTo: stack.widthAnchor, constant: -32).isActive = true
loginButton.heightAnchor.constraint(equalToConstant: 44).isActive = true
}
func logoLayout(){
logo.topAnchor.constraint(equalTo: stack.topAnchor).isActive = true
logo.heightAnchor.constraint(equalToConstant: 56).isActive = true
logo.widthAnchor.constraint(equalToConstant: 60).isActive = true
}
func stackLayout(){
stack.heightAnchor.constraint(equalTo: bottomContainer.heightAnchor).isActive = true
stack.widthAnchor.constraint(equalTo: bottomContainer.widthAnchor).isActive = true
}
func horizontalStackLayout(){
horizontalStack.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16).isActive = true
horizontalStack.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16).isActive = true
horizontalStack.heightAnchor.constraint(equalToConstant: 40).isActive = true
}
func pinFieldLayout(){
pinField.leadingAnchor.constraint(equalTo: horizontalStack.leadingAnchor).isActive = true
pinField.topAnchor.constraint(equalTo: horizontalStack.topAnchor).isActive = true
pinField.bottomAnchor.constraint(equalTo: horizontalStack.bottomAnchor).isActive = true
pinField.widthAnchor.constraint(equalToConstant: 50).isActive = true
}
func phoneFieldLayout(){
phoneFeild.leadingAnchor.constraint(equalTo: pinField.trailingAnchor, constant: 15).isActive = true
phoneFeild.topAnchor.constraint(equalTo: horizontalStack.topAnchor).isActive = true
phoneFeild.bottomAnchor.constraint(equalTo: horizontalStack.bottomAnchor).isActive = true
}
func modelLayout(){
model.topAnchor.constraint(equalTo: topContainer.topAnchor).isActive = true
model.leadingAnchor.constraint(equalTo: topContainer.leadingAnchor).isActive = true
model.trailingAnchor.constraint(equalTo: topContainer.trailingAnchor).isActive = true
model.heightAnchor.constraint(equalTo: topContainer.heightAnchor).isActive = true
}
func bottomContainerLayout(){
bottomContainer.topAnchor.constraint(equalTo: topContainer.bottomAnchor).isActive = true
bottomContainer.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
bottomContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
bottomContainer.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
}
func topContainerLayout(){
topContainer.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
topContainer.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 2/3 , constant: -20).isActive = true
topContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
topContainer.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
}
#objc func someButtonAction(){
print("Button is tapped!")
let opt = OTPViewController()
present(opt, animated: true, completion: nil)
}
}
`
If you want to customize app orientation do this:
Go to General setting of project
in Deployment , change device orientation .
Probably you didn't make auto layout.
Please check this out -> Auto Layout Guide
And this -> Programmatically Creating Constraints
Edit:
Use that translatesAutoresizingMaskIntoConstraints = false after addSubView.For example
func topContainerLayout(){
topContainer.translatesAutoresizingMaskIntoConstraints = false
topContainer.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
topContainer.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 2/3 , constant: -20).isActive = true
topContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
topContainer.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
}
I'm trying to build a custom AD when the app opens it pop up some UIViews and image and two buttons then control it from my Firebase, for now I have problem adding the adImage and buttonsStack(contains 2 buttons) inside my backView programmatically and so far nothing works ..
I need the image takes ~ %75 of the backView up and the buttonsStack ~ %25 of the rest
here some code and I have upload it to my GitHub
import UIKit
class ViewController: UIViewController {
let backroundView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .black
view.alpha = 0.5
return view
}()
let backView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .white
view.layer.cornerRadius = 15
return view
}()
let adImage: UIImageView = {
var image = UIImageView()
image.translatesAutoresizingMaskIntoConstraints = false
image.contentMode = .scaleAspectFill
return image
}()
let buttonsStack: UIStackView = {
let stack = UIStackView()
stack.translatesAutoresizingMaskIntoConstraints = false
stack.alignment = UIStackViewAlignment.fill
stack.axis = UILayoutConstraintAxis.vertical
stack.distribution = .equalSpacing
stack.spacing = 8
stack.backgroundColor = UIColor.red
return stack
}()
let actionButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Open", for: .normal)
button.setTitleColor(.white, for: .normal)
button.backgroundColor = UIColor(red: 0, green: 0.60, blue: 1, alpha: 1)
button.layer.cornerRadius = 8
button.titleLabel?.adjustsFontSizeToFitWidth = true
button.titleLabel?.textAlignment = .center
return button
}()
let dismessButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Exit", for: .normal)
button.setTitleColor(.white, for: .normal)
button.backgroundColor = .lightGray
button.layer.cornerRadius = 8
button.titleLabel?.adjustsFontSizeToFitWidth = true
button.titleLabel?.textAlignment = .center
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
func setupUI(){
// backroundView
view.addSubview(backroundView)
backroundView.frame = view.frame
// backView
view.addSubview(backView)
backView.topAnchor.constraint(equalTo: view.topAnchor, constant: 80).isActive = true
backView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -50).isActive = true
backView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -25).isActive = true
backView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 25).isActive = true
// adImage
backView.addSubview(adImage)
adImage.image = UIImage(named: "testImage")
adImage.topAnchor.constraint(equalTo: backView.topAnchor).isActive = true
adImage.rightAnchor.constraint(equalTo: backView.rightAnchor).isActive = true
adImage.leftAnchor.constraint(equalTo: backView.leftAnchor).isActive = true
adImage.heightAnchor.constraint(equalTo: backView.heightAnchor, multiplier: 0.50).isActive = true
// buttonsStack
buttonsStack.addArrangedSubview(actionButton)
buttonsStack.addArrangedSubview(dismessButton)
backView.addSubview(buttonsStack)
buttonsStack.topAnchor.constraint(equalTo: backView.topAnchor, constant: 15).isActive = true
buttonsStack.bottomAnchor.constraint(equalTo: backView.bottomAnchor, constant: -15).isActive = true
buttonsStack.rightAnchor.constraint(equalTo: backView.rightAnchor, constant: -15).isActive = true
buttonsStack.leftAnchor.constraint(equalTo: backView.leftAnchor, constant: 15).isActive = true
}
}
For the image to take 0.75 change this
adImage.heightAnchor.constraint(equalTo: backView.heightAnchor, multiplier: 0.50).isActive = true
to
adImage.heightAnchor.constraint(equalTo: backView.heightAnchor, multiplier: 0.75).isActive = true
//
then the buttonStack should goes under it so change this
buttonsStack.topAnchor.constraint(equalTo: backView.topAnchor, constant: 15).isActive = true
to
buttonsStack.topAnchor.constraint(equalTo: adImage.bottomAnchor, constant: 15).isActive = true
I'm trying to create a keyboard accessory view programmatically. I've set up a container view and inside that I'm trying to set up a textfield, post button, and an emoji.
Here's an example of what I'm trying to make.
Click here to view the image.
Here's the code that I am working with. I think the problem is when I'm setting the constraints.
Couple of questions running through my mind are:
Do I need to set up constraints for the container view?
How do I add appropriate constraints to the textfield?
override var inputAccessoryView: UIView? {
get {
//Set up the container
let containerView = UIView()
containerView.backgroundColor = #colorLiteral(red: 0.9784782529, green: 0.9650371671, blue: 0.9372026324, alpha: 1)
containerView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 60)
let textField = UITextField()
textField.placeholder = "Add a reframe..."
textField.isSecureTextEntry = false
textField.textAlignment = .left
textField.borderStyle = .none
textField.translatesAutoresizingMaskIntoConstraints = false
textField.translatesAutoresizingMaskIntoConstraints = false
textField.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true
textField.trailingAnchor.constraint(equalTo: containerView.trailingAnchor).isActive = true
textField.bottomAnchor.constraint(equalTo: containerView.bottomAnchor).isActive = true
textField.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true
textField.heightAnchor.constraint(equalToConstant: 50)
containerView.addSubview(textField)
return containerView
}
}
This is the error that I keep getting.
*** Terminating app due to uncaught exception 'NSGenericException', reason: 'Unable to activate constraint with anchors and because they have no common ancestor. Does the constraint or its anchors reference items in different view hierarchies? That's illegal.'
EDIT:
View Post Controller
lazy var containerView: CommentInputAccessoryView = {
let frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 60)
let commentInputAccessoryView = CommentInputAccessoryView(frame: frame)
commentInputAccessoryView.delegate = self
return commentInputAccessoryView
}()
//Setting up the keyboard accessory view for comments.
override var inputAccessoryView: UIView? {
get {
return containerView
}
}
override var canBecomeFirstResponder: Bool {
return true
}
CommentInputAccessoryView
protocol CommentInputAccessoryViewDelegate {
func didSend(for comment: String)
}
class CommentInputAccessoryView: UIView {
var delegate: CommentInputAccessoryViewDelegate?
func clearCommentTextView() {
commentTextView.text = nil
showPlaceholderLabel()
}
let commentTextView: UITextView = {
let text = UITextView()
text.translatesAutoresizingMaskIntoConstraints = false
//text.placeholder = "Add a reframe..."
text.textAlignment = .left
text.backgroundColor = #colorLiteral(red: 0.9784782529, green: 0.9650371671, blue: 0.9372026324, alpha: 1)
text.layer.cornerRadius = 50/2
text.layer.masksToBounds = true
text.isScrollEnabled = false
text.font = UIFont.systemFont(ofSize: 16)
text.textContainerInset = UIEdgeInsets(top: 12, left: 12, bottom: 12, right: 64)
//text.borderStyle = .none
return text
}()
let placeholderLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "Add a response..."
label.textColor = .black
label.font = UIFont.systemFont(ofSize: 16)
return label
}()
func showPlaceholderLabel() {
placeholderLabel.isHidden = false
}
let sendButton: UIButton = {
let send = UIButton(type: .system)
//let sendButton = UIImageView(image: #imageLiteral(resourceName: "arrowUp"))
send.translatesAutoresizingMaskIntoConstraints = false
send.setTitle("Send", for: .normal)
send.setTitleColor(#colorLiteral(red: 0.2901960784, green: 0.3725490196, blue: 0.937254902, alpha: 1), for: .normal)
send.contentEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 10)
send.addTarget(self, action: #selector(handlePostComment), for: .touchUpInside)
return send
}()
let hugButton: UIButton = {
let hug = UIButton()
hug.translatesAutoresizingMaskIntoConstraints = false
hug.setTitle("🤗", for: .normal)
hug.backgroundColor = #colorLiteral(red: 0.9784782529, green: 0.9650371671, blue: 0.9372026324, alpha: 1)
hug.contentEdgeInsets = UIEdgeInsets(top: 12, left: 12, bottom: 12, right: 12)
hug.layer.cornerRadius = 25
hug.layer.masksToBounds = true
return hug
}()
override init(frame: CGRect) {
super.init(frame: frame)
autoresizingMask = .flexibleHeight
addSubview(commentTextView)
addSubview(sendButton)
addSubview(hugButton)
addSubview(placeholderLabel)
if #available(iOS 11.0, *) {
commentTextView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -10).isActive = true
hugButton.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -10).isActive = true
}
else {
}
commentTextView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: 8).isActive = true
commentTextView.topAnchor.constraint(equalTo: self.topAnchor, constant: 10).isActive = true
commentTextView.trailingAnchor.constraint(equalTo: hugButton.leadingAnchor, constant: 8)
placeholderLabel.leadingAnchor.constraint(equalTo: commentTextView.leadingAnchor, constant: 18).isActive = true
placeholderLabel.centerYAnchor.constraint(equalTo: self.commentTextView.centerYAnchor).isActive = true
sendButton.trailingAnchor.constraint(equalTo: self.commentTextView.trailingAnchor, constant: -10).isActive = true
sendButton.centerYAnchor.constraint(equalTo: self.commentTextView.bottomAnchor, constant: -22).isActive = true
hugButton.leadingAnchor.constraint(equalTo: self.commentTextView.trailingAnchor, constant: 10).isActive = true
hugButton.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -8).isActive = true
hugButton.widthAnchor.constraint(equalToConstant: 40)
//hugButton.centerYAnchor.constraint(equalTo: self.commentTextView.centerYAnchor).isActive = true
NotificationCenter.default.addObserver(self, selector: #selector(handleTextChanged), name: .UITextViewTextDidChange, object: nil)
}
override var intrinsicContentSize: CGSize {
return .zero
}
#objc func handleTextChanged() {
placeholderLabel.isHidden = !self.commentTextView.text.isEmpty
}
#objc func handlePostComment() {
guard let commentText = commentTextView.text else {return}
delegate?.didSend(for: commentText)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Here are some photos that might help for what is happening.
InputAccessoryView working:
Click here
TextView expansion:
Click here.
Before applying constraints view should be in view hierarchy or error which you got will be raised. To get rid of error just do containerView.addSubview(textField) after let textField = UITextField().
Regarding example image you posted, initial solution could be something like this
override var inputAccessoryView: UIView? {
get {
//Set up the container
let containerView = UIView()
containerView.backgroundColor = #colorLiteral(red: 0.9784782529, green: 0.9650371671, blue: 0.9372026324, alpha: 1)
containerView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 60)
let textField = UITextField()
containerView.addSubview(textField)
textField.translatesAutoresizingMaskIntoConstraints = false
textField.placeholder = "Add a reframe..."
textField.textAlignment = .left
textField.backgroundColor = .white
textField.layer.cornerRadius = 50/2
textField.layer.masksToBounds = true
textField.borderStyle = .none
textField.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 8).isActive = true
textField.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -5).isActive = true
textField.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 10).isActive = true
textField.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 15, height: textField.frame.height)) // adding left padding so it's not sticked to border
textField.leftViewMode = .always
let arrow = UIImageView(image: #imageLiteral(resourceName: "arrowUp"))
containerView.addSubview(arrow)
arrow.translatesAutoresizingMaskIntoConstraints = false
arrow.trailingAnchor.constraint(equalTo: textField.trailingAnchor, constant: -10).isActive = true
arrow.centerYAnchor.constraint(equalTo: textField.centerYAnchor).isActive = true
let button = UIButton()
containerView.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("🤗", for: .normal)
button.contentEdgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
button.leadingAnchor.constraint(equalTo: textField.trailingAnchor, constant: 10).isActive = true
button.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -8).isActive = true
button.centerYAnchor.constraint(equalTo: textField.centerYAnchor).isActive = true
// Negative values for constraints can be avoided if we change order of views when applying constrains
// f.e. instead of button.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -8).isActive = true
// write containerView.trailingAnchor.constraint(equalTo: button.trailingAnchor, constant: 8).isActive = true
return containerView
}
}
Conditions:
Swift 4, Xcode 9.3
Target: iOS 11.3
UI Done Programatically
Using Constraints
My Root View Controller is a Navigation
Situation:
I wanted to float an audio player that will be visible throughout the app.
I did an AudioPlayer.swift class that contains the user interface of the audio player.
AudioPlayer.swift
import Foundation
import UIKit
import FRadioPlayer
class AudioPlayer: UIView {
let screenSize: CGRect = UIScreen.main.bounds
let playerImage: UIImageView = {
let iv = UIImageView()
iv.translatesAutoresizingMaskIntoConstraints = false
iv.contentMode = .scaleAspectFill
iv.layer.masksToBounds = true
return iv
}()
let playerTitle: UILabel = {
let l = UILabel()
l.textColor = .darkGray
l.font = UIFont.boldSystemFont(ofSize: 13)
l.translatesAutoresizingMaskIntoConstraints = false
return l
}()
let playerSeriesTitle: UILabel = {
let l = UILabel()
l.textColor = .darkGray
l.font = UIFont.boldSystemFont(ofSize: 12)
l.translatesAutoresizingMaskIntoConstraints = false
return l
}()
override init(frame: CGRect) {
super.init(frame: frame)
translatesAutoresizingMaskIntoConstraints = false
setupAudioControls()
}
private func setupAudioControls(){
let appDelegate = AppDelegate.sharedInstance
self.backgroundColor = UIColor.init(hex: "#EBE4D3")
self.addSubview(playerImage)
self.addSubview(playerTitle)
self.addSubview(playerSeriesTitle)
self.heightAnchor.constraint(equalToConstant: 150).isActive = true
self.bottomAnchor.constraint(equalTo: appDelegate().rootView ).isActive = true
self.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor).isActive = true
self.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor).isActive = true
playerImage.topAnchor.constraint(equalTo: self.topAnchor, constant: 10).isActive = true
playerImage.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10).isActive = true
playerImage.widthAnchor.constraint(equalToConstant: 55).isActive = true
playerImage.heightAnchor.constraint(equalToConstant: 55).isActive = true
playerTitle.topAnchor.constraint(equalTo: self.topAnchor, constant: 5).isActive = true
playerTitle.leadingAnchor.constraint(equalTo: playerImage.trailingAnchor, constant: 10).isActive = true
playerTitle.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 10).isActive = true
playerTitle.heightAnchor.constraint(equalToConstant: 25).isActive = true
playerSeriesTitle.topAnchor.constraint(equalTo: playerTitle.topAnchor, constant: 20).isActive = true
playerSeriesTitle.leadingAnchor.constraint(equalTo: playerImage.trailingAnchor, constant: 10).isActive = true
playerSeriesTitle.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 10).isActive = true
playerSeriesTitle.heightAnchor.constraint(equalToConstant: 20).isActive = true
UIView.animate(withDuration: 0.5, animations: {
self.frame.origin.y -= 150
self.playerImage.frame.origin.y -= 150
self.playerTitle.frame.origin.y -= 150
self.playerSeriesTitle.frame.origin.y -= 150
}, completion: nil)
self.setNeedsLayout()
self.reloadInputViews()
}
}
Problem:
How can I add this to the Root View Controller to stay on top in all view controllers that I have in my app? Wherever I navigate, the player must stay on the bottom part of every controller. As you can see, I need a reference to the rootviewcontroller to set the contraints for the AudioPlayer but I failed in so many attempts (like calling the rootviewcontroller using AppDelegate)
I update it for you
add singleton static let shared = AudioPlayer()
add public func showAudioPlayer () --> to display Audio player
add as subview to UIApplication.shared.keyWindow?
TODO- add HideAudioPlayer()
Use like this
AudioPlayer.shared.showAudioPlayer()
Here is updated code
import Foundation
import UIKit
class AudioPlayer: UIView {
static let shared = AudioPlayer()
let screenSize: CGRect = UIScreen.main.bounds
let playerImage: UIImageView = {
let iv = UIImageView()
iv.translatesAutoresizingMaskIntoConstraints = false
iv.contentMode = .scaleAspectFill
iv.layer.masksToBounds = true
return iv
}()
let playerTitle: UILabel = {
let l = UILabel()
l.textColor = .darkGray
l.font = UIFont.boldSystemFont(ofSize: 13)
l.translatesAutoresizingMaskIntoConstraints = false
return l
}()
let playerSeriesTitle: UILabel = {
let l = UILabel()
l.textColor = .darkGray
l.font = UIFont.boldSystemFont(ofSize: 12)
l.translatesAutoresizingMaskIntoConstraints = false
return l
}()
override init(frame: CGRect) {
super.init(frame: frame)
translatesAutoresizingMaskIntoConstraints = false
// setupAudioControls()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public func showAudioPlayer (){
self.setupAudioControls()
}
private func setupAudioControls(){
self.backgroundColor = .red
self.addSubview(playerImage)
self.addSubview(playerTitle)
self.addSubview(playerSeriesTitle)
UIApplication.shared.keyWindow?.addSubview(self)
if let layoutGuide = UIApplication.shared.keyWindow?.layoutMarginsGuide {
self.heightAnchor.constraint(equalToConstant: 150).isActive = true
self.bottomAnchor.constraint(equalTo: layoutGuide.bottomAnchor ).isActive = true
self.leadingAnchor.constraint(equalTo: layoutGuide.leadingAnchor).isActive = true
self.trailingAnchor.constraint(equalTo: layoutGuide.trailingAnchor).isActive = true
}
playerImage.topAnchor.constraint(equalTo: self.topAnchor, constant: 10).isActive = true
playerImage.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10).isActive = true
playerImage.widthAnchor.constraint(equalToConstant: 55).isActive = true
playerImage.heightAnchor.constraint(equalToConstant: 55).isActive = true
playerTitle.topAnchor.constraint(equalTo: self.topAnchor, constant: 5).isActive = true
playerTitle.leadingAnchor.constraint(equalTo: playerImage.trailingAnchor, constant: 10).isActive = true
playerTitle.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 10).isActive = true
playerTitle.heightAnchor.constraint(equalToConstant: 25).isActive = true
playerSeriesTitle.topAnchor.constraint(equalTo: playerTitle.topAnchor, constant: 20).isActive = true
playerSeriesTitle.leadingAnchor.constraint(equalTo: playerImage.trailingAnchor, constant: 10).isActive = true
playerSeriesTitle.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 10).isActive = true
playerSeriesTitle.heightAnchor.constraint(equalToConstant: 20).isActive = true
UIView.animate(withDuration: 0.5, animations: {
self.frame.origin.y -= 150
self.playerImage.frame.origin.y -= 150
self.playerTitle.frame.origin.y -= 150
self.playerSeriesTitle.frame.origin.y -= 150
}, completion: nil)
self.setNeedsLayout()
self.reloadInputViews()
}
}
If you want to show view in each view controller then as per view hierarchy you must have to add in UIWindow. UIWindow is base of all screen.
AppDelegate.shared.window?.addSubview(AudioPlayer)
You can add your view to UIWindow.
I am doing the same thing with below method in AppDelegate.
var window: UIWindow?
func addPlayerViewAtBottom() {
var bottomView : PlayerBottomView!
bottomView = PlayerBottomView(frame: CGRect(x: 0, y: UIScreen.main.bounds.size.height - 60, width: UIScreen.main.bounds.width, height: 60))
self.window?.addSubview(bottomView)
self.window?.bringSubview(toFront: bottomView)
}