I am unable to trigger the animation, I put the function below outside the viewdidload function and created a custom function that will be trigger when the button is tapped. But instead of it creating the animation it gave me an error below. this is the error
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
this is the function that going to be triggered
#objc func triggeranimation(){
UIView.animate(withDuration: 0.5, animations: {
let intro:introViewController = introViewController()
intro.Title.backgroundColor = UIColor.blue
}, completion: nil)
}
and I added button programmatically inside viewdidload that will trigger the function above. I try to put the custom func above before and after viewdidload function with the same error result. please help why is it telling me that it found nil when I clearly in the same view controller.
let nextBtn = UIButton(frame: CGRect(x:0, y: 0, width: 0, height: 0))
nextBtn.translatesAutoresizingMaskIntoConstraints = false
nextBtn.setTitle("Next", for: UIControlState.normal)
nextBtn.backgroundColor = UIColor.blue
nextBtn.addTarget(self, action: Selector("triggeranimation"), for: .touchUpInside)
nextBtn.setTitleColor(UIColor.white, for: .normal)
self.view.addSubview(nextBtn)
// width cant change for some unknown reason
nextBtn.widthAnchor.constraint(equalToConstant: 10)
nextBtn.heightAnchor.constraint(equalToConstant: 20)
nextBtn.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -20).isActive = true
nextBtn.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -10).isActive = true
complete code
class introViewController: UIViewController{
var Title:UILabel!
var nextBtn:UIButton!
override func viewDidLoad() {
super.viewDidLoad()
let Title = UILabel(frame: CGRect(x: self.view.frame.size.width / 2, y: (self.view.frame.size.height + 30), width: 50, height: 10))
Title.translatesAutoresizingMaskIntoConstraints = false
Title.text = "WELCOME"
Title.font = UIFont.systemFont(ofSize: 24, weight: .bold)
Title.textAlignment = .center
Title.textColor = UIColor.black
self.view.addSubview(Title)
Title.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -250).isActive = true
Title.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0).isActive = true
let nextBtn = UIButton(frame: CGRect(x:0, y: 0, width: 0, height: 0))
nextBtn.translatesAutoresizingMaskIntoConstraints = false
nextBtn.setTitle("Next", for: UIControlState.normal)
nextBtn.backgroundColor = UIColor.blue
nextBtn.addTarget(self, action: Selector("triggeranimation"), for: .touchUpInside)
nextBtn.setTitleColor(UIColor.white, for: .normal)
self.view.addSubview(nextBtn)
// width cant change for some unknown reason
nextBtn.widthAnchor.constraint(equalToConstant: 10)
nextBtn.heightAnchor.constraint(equalToConstant: 20)
nextBtn.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -20).isActive = true
nextBtn.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -10).isActive = true
}
#objc func triggeranimation(){
UIView.animate(withDuration: 0.5, animations: {
let intro:introViewController = introViewController()
intro.Title.backgroundColor = UIColor.blue
}, completion: nil)
}
}
in addition, i also tried to move UIView.animate inside the viewdidload but it did not animate at all, it just change the color instantly.
UPDATE: i did solve the found nil problem, by changing Title variable
var Title:UILabel! into var Title:UILabel! = UILabel()
but i still have a problem with animation, it did not make any animation just instantly change the color even when i make the DURATION longer into 5.0
The problem is that intro is a newly initialized view controller which is completely unrelated to the instance in question. For example, viewDidLoad will not be called on that new instance. Instead, you just want to use "self", meaning THE loaded view controller.
#objc func triggeranimation(){
UIView.animate(withDuration: 0.5, animations: {
//let intro:introViewController = introViewController()
self.Title.backgroundColor = UIColor.blue
}, completion: nil)
}
There also seems to be a problem in your viewDidLoad code, where you are trying to initialize the Title and nextBtn properties. Instead, you are accidentally creating a local constant of the same name. This is why your properties are nil.
// You have this now, which creates a local constant
//let Title = UILabel(..
// You want this instead, which initializes your property
self.Title = UILabel(..
Do the same for nextBtn, of course.
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.
So I define my UITextField, and call it in my viewDidLoad function. From there I also call a UIButton that when pressed it reads the value of the text field as 'print(myTextField.text!). If I change the value in the text field again and hit the button, the value of myTextField.text! is still the same as before. Here is my text field and here is my button, im not sure how to implement an update call. My question is how can I read the updated value and why doesn't it update in the first place?
let myTextField: UITextField = {
let UTextFieldDesign = UITextField()
UTextFieldDesign.textColor = .black
return UTextFieldDesign
}()
let loginButton: UIButton = {
let LButton = UIButton(type: .system)
LButton.layer.cornerRadius = 25.0
LButton.setTitleColor(.white, for: .normal)
LButton.setTitle(NSLocalizedString("Check Text", comment: ""), for: .normal)
LButton.titleLabel?.font = .systemFont(ofSize: 20)
LButton.backgroundColor = LoginButtonColor
LButton.addTarget(self, action: #selector(loginAction), for: .touchUpInside)
return LButton
}()
override func viewDidLoad() {
super.viewDidLoad()
self.hideKeyboardWhenTap()
setupMyTextField() //this just adds the constraints
setupLoginButton()
#objc func loginAction() {
print(myTextField.text!)
}
fileprivate func setupMyTextField() {
view.addSubview(myTextField)
myTextField.translatesAutoresizingMaskIntoConstraints = false
myTextField.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20).isActive = true
myTextField.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 20).isActive = true
myTextField.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -20).isActive = true
}
fileprivate func setupLoginButton() {
view.addSubview(loginButton)
loginButton.anchors(top: myTextField.bottomAnchor, topPad: 15, bottom: nil, bottomPad: 0,
left: view.leftAnchor, leftPad: 20, right: view.rightAnchor,
rightPad: -20, height: 50, width: 0)
loginButton.translatesAutoresizingMaskIntoConstraints = false
}
I have a textView and I have a line, I set the line's frame without contraints and set textView frame with constraints. Simply what I want is the textView to follow the line, so I put a bottomAnchor to textView equal to the topAnchor of the line. Yet when I animate the line the textView does not follow? What am I doing wrong?
var button = UIButton()
var testLine = UIView()
let textView = UITextView()
var textViewBottomAnchorConstraint: NSLayoutConstraint?
override func viewDidLoad() {
super.viewDidLoad()
testLine.backgroundColor = .black
testLine.frame = CGRect(x: 0, y: 335, width: UIScreen.main.bounds.width, height: 10)
view.addSubview(testLine)
view.addSubview(textView)
textView.frame = .zero//CGRect(x: CGFloat(integerLiteral: 16), y: CGFloat(integerLiteral: 300), width: CGFloat(integerLiteral: 282), height: CGFloat(integerLiteral: 35))
textView.backgroundColor = UIColor.yellow
textView.text = ""
textView.font = UIFont(name: "Arial Rounded MT Bold", size: 15)
textView.translatesAutoresizingMaskIntoConstraints = false
textView.isHidden = false
textView.translatesAutoresizingMaskIntoConstraints = false
// textView.bottomAnchor.constraint(equalTo: testLine.topAnchor, constant: 0).isActive = true
textView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor, constant: 20).isActive = true
textView.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor, constant: -20).isActive = true
textView.heightAnchor.constraint(equalToConstant: 40).isActive = true
textViewBottomAnchorConstraint = textView.bottomAnchor.constraint(equalTo: testLine.topAnchor, constant: 0)
textViewBottomAnchorConstraint?.isActive = true
UIView.animate(withDuration: 2, delay: 2, options: .curveEaseIn, animations: {
self.testLine.transform = CGAffineTransform.identity.translatedBy(x: 0, y: 30)
}) { (true) in
self.view.layoutIfNeeded()
}
}
As #Vollan correctly said animating transform property is not the best option. Here is quote from Apple documentation: "In iOS 8.0 and later, the transform property does not affect Auto Layout. Auto layout calculates a view’s alignment rectangle based on its untransformed frame." Therefore animation of transform property doesn't change layout of textView. I recommend you to animate frame property instead of transform.
However, if you switch to frame animation it doesn't fix all your problems. If you keep your animation inside viewDidLoad method you may encounter very strange behavior. The reason is that in viewDidLoad the view itself is not yet laid out properly. Starting animation inside viewDidLoad may lead to unpredicted results.
At last you need adjust your animation block. Apple recommends to apply layoutIfNeeded inside the animation block. Or at least they used to recommend it then autolayout was introduced - watch this WWDC video (starting from 30th minute) for further details.
If you apply all recommendations above your code should look like this:
var button = UIButton()
var testLine = UIView()
let textView = UITextView()
var textViewBottomAnchorConstraint: NSLayoutConstraint?
var triggeredAnimation = false
override func viewDidLoad() {
super.viewDidLoad()
testLine.backgroundColor = .black
testLine.frame = CGRect(x: 0, y: 335, width: UIScreen.main.bounds.width, height: 10)
view.addSubview(testLine)
view.addSubview(textView)
textView.frame = .zero//CGRect(x: CGFloat(integerLiteral: 16), y: CGFloat(integerLiteral: 300), width: CGFloat(integerLiteral: 282), height: CGFloat(integerLiteral: 35))
textView.backgroundColor = UIColor.yellow
textView.text = ""
textView.font = UIFont(name: "Arial Rounded MT Bold", size: 15)
textView.translatesAutoresizingMaskIntoConstraints = false
textView.isHidden = false
textView.translatesAutoresizingMaskIntoConstraints = false
// textView.bottomAnchor.constraint(equalTo: testLine.topAnchor, constant: 0).isActive = true
textView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor, constant: 20).isActive = true
textView.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor, constant: -20).isActive = true
textView.heightAnchor.constraint(equalToConstant: 40).isActive = true
textViewBottomAnchorConstraint = textView.bottomAnchor.constraint(equalTo: testLine.topAnchor, constant: 0)
textViewBottomAnchorConstraint?.isActive = true
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// viewDidAppear may be called several times during view controller lifecycle
// triggeredAnimation ensures that animation will be called just once
if self.triggeredAnimation {
return
}
self.triggeredAnimation = true
let oldFrame = self.testLine.frame
UIView.animate(withDuration: 2, delay: 2, options: .curveEaseIn, animations: {
self.testLine.frame = CGRect(x: oldFrame.minX, y: oldFrame.minY + 30, width: oldFrame.width,
height: oldFrame.height)
self.view.layoutIfNeeded()
})
}
Anchor points make references to others positions, meaning. It is still referensed to y = 355 as you transform it and not actually "move" it.
What i recommend is that you don't mix using frame-based layout and anchorpoints / layout constraints.
Essentially I am using this code extension below to copy a view and all its subviews. The copy is successful and I am able to view the copied view. However, the button on each copied view cannot be pressed. The button can only be pressed within the first original (not copied) view.
How to get all copied buttons to be active? Is this even possible?
I have already tried .isUserInteractionEnabled on the button and its parent view.
override func viewDidLoad(){
super.viewDidLoad()
view.isUserInteractionEnabled = true
view.addSubview(containerScrollView)
containerScrollView.addSubview(contentView)
contentView.addSubview(stackMainView)
let button = UIButton(frame: CGRect(x: 270, y: 200, width: 80, height: 40))
let partLabel1 = UILabel(frame: CGRect(x:10, y: 10, width: 300, height: 50))
let partLabel2 = UILabel(frame: CGRect(x:10, y: 50, width: 300, height: 50))
partLabel1.text = "This should sit within part use :)"
partLabel1.textColor = .white
partLabel2.text = "This should also sit within part use :)"
partLabel2.textColor = .white
contentView.addSubview(button)
contentView.addSubview(partLabel1)
contentView.addSubview(partLabel2)
part.addSubview(button)
part.addSubview(partLabel1)
part.addSubview(partLabel2)
part.bringSubviewToFront(button)
part.bringSubviewToFront(partUse3Label1)
part.layer.zPosition = -1
button.setTitle("Issue", for: .normal)
button.backgroundColor = .orange
button.leadingAnchor.constraint(equalTo: part.leadingAnchor).isActive = true
button.trailingAnchor.constraint(equalTo: part.trailingAnchor).isActive = true
button.bottomAnchor.constraint(equalTo: part.bottomAnchor).isActive = true
button.topAnchor.constraint(equalTo: part.topAnchor).isActive = true
button.heightAnchor.constraint(equalToConstant: 40).isActive = true
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
button.isUserInteractionEnabled = true
part.bringSubviewToFront(button)
partLabel1.leadingAnchor.constraint(equalTo: part.leadingAnchor).isActive = true
partLabel1.trailingAnchor.constraint(equalTo: part.trailingAnchor).isActive = true
partLabel2.leadingAnchor.constraint(equalTo: part.leadingAnchor).isActive = true
partLabel2.trailingAnchor.constraint(equalTo: part.trailingAnchor).isActive = true
part.layoutIfNeeded()
let copiedView = self.part.copyView()
stackMainView.addArrangedSubview(part)
stackMainView.addArrangedSubview(copiedView)
containerScrollView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
containerScrollView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0).isActive = true
containerScrollView.trailingAnchor.constraint(equalTo:self.view.trailingAnchor, constant: 0).isActive = true
containerScrollView.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
contentView.topAnchor.constraint(equalTo: self.containerScrollView.topAnchor, constant: 0).isActive = true
contentView.leadingAnchor.constraint(equalTo: self.containerScrollView.leadingAnchor, constant: 0).isActive = true
contentView.trailingAnchor.constraint(equalTo:self.containerScrollView.trailingAnchor, constant: 0).isActive = true
contentView.bottomAnchor.constraint(equalTo: self.containerScrollView.bottomAnchor, constant: 0).isActive = true
contentView.widthAnchor.constraint(equalTo:self.view.widthAnchor, constant: 0).isActive = true
stackMainView.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 50).isActive = true
stackMainView.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 8).isActive = true
stackMainView.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: -8).isActive = true
stackMainView.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -30).isActive = true
}
#objc func buttonAction(sender: UIButton!) {
print("Button tapped")
}
var containerScrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.backgroundColor = .white
scrollView.isScrollEnabled = true
return scrollView
}()
var contentView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = UIColor.white
return view
}()
let stackMainView: UIStackView = {
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.backgroundColor = .random()
return stackView
}()
let part: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.layer.cornerRadius = 4
view.layer.masksToBounds = true
view.backgroundColor = .random()
return view
}()
Extension to copy the view.
extension UIView {
func copyView<T: UIView>() -> T {
return NSKeyedUnarchiver.unarchiveObject(with:
NSKeyedArchiver.archivedData(withRootObject: self)) as! T
}
}
I expect the output to print in the console "Button Tapped"
This happens only when I press the button on the non copied view.
Since this is all done in viewDidLoad, I assume that the view you want to copy is the same every time.
Your code does not work likely because NSKeyedArchiver does not archive the button's target and selector pairs.
You can create a method that gives a new UIView instead:
func createPart() -> UIView {
let part = UIView()
part.translatesAutoresizingMaskIntoConstraints = false
part.layer.cornerRadius = 4
part.layer.masksToBounds = true
part.backgroundColor = .random()
// The part below is copied from your viewDidLoad method
// Include only those lines that create the part view.
// I might have put more than you need. Check twice
let button = UIButton(frame: CGRect(x: 270, y: 200, width: 80, height: 40))
let partLabel1 = UILabel(frame: CGRect(x:10, y: 10, width: 300, height: 50))
let partLabel2 = UILabel(frame: CGRect(x:10, y: 50, width: 300, height: 50))
partLabel1.text = "This should sit within part use :)"
partLabel1.textColor = .white
partLabel2.text = "This should also sit within part use :)"
partLabel2.textColor = .white
part.addSubview(button)
part.addSubview(partLabel1)
part.addSubview(partLabel2)
part.bringSubviewToFront(button)
part.bringSubviewToFront(partUse3Label1)
part.layer.zPosition = -1
button.setTitle("Issue", for: .normal)
button.backgroundColor = .orange
button.leadingAnchor.constraint(equalTo: part.leadingAnchor).isActive = true
button.trailingAnchor.constraint(equalTo: part.trailingAnchor).isActive = true
button.bottomAnchor.constraint(equalTo: part.bottomAnchor).isActive = true
button.topAnchor.constraint(equalTo: part.topAnchor).isActive = true
button.heightAnchor.constraint(equalToConstant: 40).isActive = true
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
button.isUserInteractionEnabled = true
part.bringSubviewToFront(button)
partLabel1.leadingAnchor.constraint(equalTo: part.leadingAnchor).isActive = true
partLabel1.trailingAnchor.constraint(equalTo: part.trailingAnchor).isActive = true
partLabel2.leadingAnchor.constraint(equalTo: part.leadingAnchor).isActive = true
partLabel2.trailingAnchor.constraint(equalTo: part.trailingAnchor).isActive = true
part.layoutIfNeeded()
return part
}
And then in viewDidLoad, you should remove the lines of code that help create the part view, leaving only the code that creates the stack view and main content view. You should then call createPart twice, and there you have 2 copies!
let part = createPart()
let copyOfPart = createPart()
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
}
}