UIButton selector function weird behaviour - ios

In my application, i had a UIButton that works perfectly fine on
XCode simulator (mostly with iOS 11.4 and iOS 14.5)
physical device (iPhone 6: iOS 12.5, iPhone 7: iOS 14.5, iPhone 12: iOS 14.5)
My trouble is that some of my client's phone does not work as expected
iPhone XR: iOS 14.5
iPhone XS MAX: iOS 14.5
iPhone 8 Plus: iOS 14.4
Expected Behaviour -
User clicked the button > call selector function > trigger callback > go to another view controller
Client's Phone Behaviour -
User clicked the button, and nothing happen. I'm pretty sure the app had captured the touch, because there's some shadow on button pressed.
Below is the sample codes i use
MainViewController : The purpose of this controller is to swap between different child view controller
class MainViewController: UIViewController {
private let containerView: UIView = {
let view = UIView(frame: .zero)
return view
}()
private var currentContentVC: UIViewController
init() {
let childVC = ChildViewController()
currentContentVC = childVC
childVC.delegate = self
super.init(nibName: nil, bundle: nil)
}
override func loadView() {
super.loadView()
view.addSubview(containerView);
containerView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
containerView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true
currentContentVC.view.frame = containerView.bounds
addChild(currentContentVC)
containerView.addSubview(currentContentVC.view)
}
}
extension MainViewController: ChildViewControllerCallBack {
func btnPressed() {
let oldVC = currentContentVC
let newVC = OtherChildViewController()
newVC.delegate = self
newVC.view.frame = containerView.bounds
currentContentVC = newVC
switchChildController(from: oldVC, to: currentContentVC, options: options)
}
}
ChildViewController : Controller that consist of the troubled button
class ChildViewController: UIViewController {
weak var delegate: ChildViewControllerCallBack?
private let mainV: UIView = {
let view = UIView()
return view
}()
private let detailV: UIView = {
let view = UIView()
return view
}()
private let myBtn: UIButton = {
let button = UIButton(type: .custom)
let buttonImage = UIImage(named: "btn_gold_square")
let imageRatio = (buttonImage?.size.width)! / (buttonImage?.size.height)!
button.setBackgroundImage(buttonImage, for: .normal)
button.titleLabel?.font = Font.dynamicXSmallFont
button.titleLabel?.numberOfLines = 2
button.titleLabel?.textAlignment = .center
button.setTitle(title, for: .normal)
button.setTitleColor(Colors.black, for: .normal)
let imageWidth = (SharedData.shared().dialogWidth + (SharedData.shared().dialogWidth * 0.1)) * 0.25
let imageHeight = imageWidth / imageRatio
button.widthAnchor.constraint(equalToConstant: imageWidth).isActive = true
button.heightAnchor.constraint(equalToConstant: imageHeight).isActive = true
button.addTarget(self, action: #selector(btnPressed), for: .touchUpInside)
return button
}()
protocol hildViewControllerCallBack: class {
func btnPressed()
}
override func loadView() {
super.loadView()
view.addSubview(mainV);
mainV.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
mainV.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
mainV.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
mainV.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true
[detailV, myBtn].forEach { mainV.addSubview(0) }
detailV.topAnchor.constraint(equalTo: mainV.topAnchor, constant: 0).isActive = true
detailV.leadingAnchor.constraint(equalTo: mainV.leadingAnchor, constant: 0).isActive = true
detailV.bottomAnchor.constraint(equalTo: mainV.bottomAnchor, constant: 0).isActive = true
detailV.widthAnchor.constraint(equalTo: mainV.widthAnchor, multiplier: 0.7).isActive = true
detailV.centerYAnchor.constraint(equalTo: mainV.centerYAnchor).isActive = true
myBtn.leadingAnchor.constraint(equalTo: detailV.trailingAnchor, constant: 0).isActive = true
myBtn.trailingAnchor.constraint(equalTo: mainV.trailingAnchor, constant: 0).isActive = true
}
#objc private func btnPressed(){
delegate?.btnPressed()
}
}

Finally solved it, the problematic line is this
private let myBtn: UIButton = {
All I have to do is change it to
lazy var myBtn: UIButton = {
If you wondering what and how does it work, for the long version, please refer to this article.
The short version:
A lazy stored property is a property whose initial value is not calculated until the first time it is used. You indicate a lazy stored property by writing the lazy modifier before its declaration.

Related

Need to add Label on UIView programatically based on some conditions using swift

I am working on a canvas view where I am trying to add image to a UIView and a text to a UIView, on satisfying some conditions.
The issue is, I am not able to add label to my UIView after satisfying the required condition. I tried this code separately without any functions and conditions and it is working fine. But if I try and run it as given below it doesn't show me the label on the UIView.
[Note- The code here is working fine for imageView.]
I tried various constraint combinations like, centerX, centerY, width, height or leading, trailing, top, bottom. But nothing is working. Please help.
My code is as below: -
class CanvasViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
createCanvasOnAspectRatio()
}
func createCanvasOnAspectRatio() {
let View1: UIView = {
let viewView = UIView()
viewView.translatesAutoresizingMaskIntoConstraints = false
viewView.contentMode = .scaleAspectFit
print(UserDefaults.standard.bool(forKey: "backgroundColourSelected"))
if UserDefaults.standard.bool(forKey: "backgroundColourSelected") {
viewView.backgroundColor = self.viewColor
}else {
viewView.backgroundColor = .white
}
viewView.clipsToBounds = true
return viewView
}()
self.canvasView.addSubview(View1)
View1.centerXAnchor.constraint(equalTo: canvasView.centerXAnchor, constant: 0).isActive = true
View1.centerYAnchor.constraint(equalTo: canvasView.centerYAnchor, constant: 0).isActive = true
View1.widthAnchor.constraint(equalTo: canvasView.widthAnchor, constant: 0).isActive = true
View1.heightAnchor.constraint(equalTo: canvasView.widthAnchor, multiplier: aspectRatio).isActive = true
if UserDefaults.standard.bool(forKey: "imageSelectedFromGallexy") == true {
for item in 0..<self.imagesForGallery.count {
let image = imagesForGallery[item]
let image_View: UIImageView = {
let imageV = UIImageView()
imageV.image = image
imageV.contentMode = .scaleAspectFill
imageV.translatesAutoresizingMaskIntoConstraints = false
imageV.clipsToBounds = true
return imageV
}()
View1.addSubview(image_View)
image_View.topAnchor.constraint(equalTo: View1.topAnchor, constant: 0).isActive = true
image_View.bottomAnchor.constraint(equalTo: View1.bottomAnchor, constant: 0).isActive = true
image_View.leadingAnchor.constraint(equalTo: View1.leadingAnchor, constant: 0).isActive = true
image_View.trailingAnchor.constraint(equalTo: View1.trailingAnchor, constant: 0).isActive = true
}
}
if UserDefaults.standard.bool(forKey: "addTextToCanvas") == true {
let labelTextView: UILabel = {
let labelView = UILabel()
//labelView.frame = CGRect(x: 0.0, y: 0.0, width: 50.0, height: 30.0)
labelView.translatesAutoresizingMaskIntoConstraints = false
labelView.textColor = .green
labelView.text = "Hello"
labelView.font = UIFont(name: "Avenir-Medium", size: 15.0)
labelView.textAlignment = .center
// labelView.clipsToBounds = true
return labelView
}()
View1.addSubview(labelTextView)
labelTextView.centerXAnchor.constraint(equalTo: View1.centerXAnchor, constant: 0).isActive = true
labelTextView.centerYAnchor.constraint(equalTo: View1.centerYAnchor, constant: 0).isActive = true
labelTextView.widthAnchor.constraint(equalTo: View1.widthAnchor, constant: 0).isActive = true
labelTextView.heightAnchor.constraint(equalTo: View1.widthAnchor, multiplier: 1.0).isActive = true
UserDefaults.standard.set(false, forKey: "addTextToCanvas")
}
}
}

Popover transparency issue only for iPhone 11 and Max versions

I am facing a weird issue. There is an UIPopoverPresentationController and I have this situation:
It is working fine on Portrait, but on Landscape, I lose the background and the layout.
AND it is happening only for iPhone 11 and Max version, it is fine for iPhone X, 11 pro, 12, etc.
Obs: If I set the popover background color, it works with the new color, but why the background is transparent only on these versions above with the layout different?
I did a POC with a simple code to reproduce the problem:
ViewController
#IBAction func showPopover(sender: AnyObject) {
let popVC = PopoverViewController.init()
popVC.modalPresentationStyle = .popover
let popOverVC = popVC.popoverPresentationController
popOverVC?.delegate = self
popOverVC?.sourceView = view
popOverVC?.sourceRect = sender.frame
popVC.preferredContentSize = CGSize(width: 250, height: 250)
self.present(popVC, animated: true)
}
PopoverViewController
override func viewDidLoad() {
let view2 = UIView.init()
let view3 = UIView.init()
view2.backgroundColor = .blue
view2.alpha = 0.5
view3.backgroundColor = .blue
view3.alpha = 0.5
view.addSubview(view2)
view.addSubview(view3)
view2.translatesAutoresizingMaskIntoConstraints = false
view2.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 5).isActive = true
view2.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -5).isActive = true
view2.topAnchor.constraint(equalTo: view.topAnchor, constant: 25).isActive = true
view2.heightAnchor.constraint(equalToConstant: 30).isActive = true
view3.translatesAutoresizingMaskIntoConstraints = false
view3.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 5).isActive = true
view3.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -5).isActive = true
view3.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -25).isActive = true
view3.heightAnchor.constraint(equalToConstant: 30).isActive = true
}
public func getPopover(sourceView: UIView) {
modalPresentationStyle = .popover
let popoverPresentation = self.popoverPresentationController
popoverPresentation?.sourceView = sourceView
popoverPresentation?.sourceRect = sourceView.bounds
}

Swift button from nested UIView is not clickable

I have a ViewController and another UIView called MySubview which contains a simple button.
If I add MySubview into ViewController the button is unclickable. However if I put the button directly inside ViewController everything works as expected.
Example:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let button = MySubview()
view.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
button.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
button.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
}
}
class MySubview: UIView {
let button: UIButton = {
let button = UIButton()
button.setTitle("MyButton", for: .normal)
button.setTitleColor(.label, for: .normal)
button.addTarget(self, action: #selector(myfunc), for: .touchUpInside)
return button
}()
#objc func myfunc() {
print("clicked")
}
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .red
addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
button.topAnchor.constraint(equalTo: topAnchor, constant: 0).isActive = true
button.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
When I click on the button I do not see printed message.
Your code is confused, if I understand well take a look to my code below, declare your button and your view under your controller class:
class ViewController: UIViewController {
let button: UIButton = {
let button = UIButton()
button.setTitle("MyButton", for: .normal)
button.setTitleColor(.white, for: .normal)
button.backgroundColor = .black
button.addTarget(self, action: #selector(myfunc), for: .touchUpInside)
return button
}()
let mysubview = UIView()
...
now in viewDidLoad set constraints like this:
override func viewDidLoad() {
super.viewDidLoad()
mysubview.backgroundColor = .red
view.addSubview(mysubview)
mysubview.translatesAutoresizingMaskIntoConstraints = false
mysubview.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
mysubview.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
mysubview.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true
mysubview.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
mysubview.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
button.topAnchor.constraint(equalTo: mysubview.topAnchor, constant: 20).isActive = true
button.leadingAnchor.constraint(equalTo: mysubview.leadingAnchor, constant: 20).isActive = true
button.trailingAnchor.constraint(equalTo: mysubview.trailingAnchor, constant: -20).isActive = true
button.heightAnchor.constraint(equalToConstant: 50).isActive = true
}
now add your func
#objc func myfunc() {
print("clicked")
}
complete code:
import UIKit
class ViewController: UIViewController {
let button: UIButton = {
let button = UIButton()
button.setTitle("MyButton", for: .normal)
button.setTitleColor(.white, for: .normal)
button.backgroundColor = .black
button.addTarget(self, action: #selector(myfunc), for: .touchUpInside)
return button
}()
let mysubview = UIView()
override func viewDidLoad() {
super.viewDidLoad()
mysubview.backgroundColor = .red
view.addSubview(mysubview)
mysubview.translatesAutoresizingMaskIntoConstraints = false
mysubview.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
mysubview.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
mysubview.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true
mysubview.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
mysubview.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
button.topAnchor.constraint(equalTo: mysubview.topAnchor, constant: 20).isActive = true
button.leadingAnchor.constraint(equalTo: mysubview.leadingAnchor, constant: 20).isActive = true
button.trailingAnchor.constraint(equalTo: mysubview.trailingAnchor, constant: -20).isActive = true
button.heightAnchor.constraint(equalToConstant: 50).isActive = true
}
#objc func myfunc() {
print("clicked")
}
}
If your subview has a frame of size zero, you won't be able to tap the button even if you can see it. Using a view debugger from Xcode will show you if this is the issue.
Also, check if the subview has user interaction enabled when adding it.
I think , the problem is in layers (I mean, subviews). Try this instead of view.addSubview(button) :
view.insertSubview(your_button_view, at: view.subviews.count)

How do I collapse UIViews programatically?

I'm working on my first IOS app and have run into an issue. I have a quite elaborate programmatic autoloyout UI that responds to user interaction. When a keyboard is shown certain Views must be collapsed, others moved and others spawned into existence based on a few conditions.
Now in it's default state no autolayout errors occur. But once things start moving it all comes apart. A few of the issues have to do with images retaining their height, while their view's heigconstriant is set to 0. Now I do have .scaleToFill enabled.
I have looked into stackViews however since most of my Views are of a different size with different nested UI elements stackviews do now appear to solve my issues. But I would certainly like some input on that.
Now my questions is: How do I collapse UIView and UIImageviews dynamically and programatically?
Now I don't mind typing out a lot of constraints manually, as long as it works.
Here are the constraints of the Views in question(there are more)
func setUpLayout() {
// SuggestionCloud
suggestionCloud.setConstraints(
topAnchor: textView.bottomAnchor, topConstant: 0,
bottomAnchor: bottomMenu.topAnchor, bottomConstant: 0,
trailingAnchor: view.trailingAnchor, trailingConstant: -10,
leadingAnchor: view.leadingAnchor, leadingConstant: 10)
print("Suggestion View frame :\(suggestionCloud.frame)")
//WEIGHT_IMAGE_VIEW
weigtImageView.topAnchor.constraint(equalTo: view.topAnchor, constant: 30).isActive = true
weigtImageView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10).isActive = true
weigtImageView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10).isActive = true
weigtImageView.heightAnchor.constraint(equalToConstant: 150).isActive = true
weigtImageView.addSubview(weightLabel);
print("Weight Image View \(weigtImageView.frame)")
//WEIGHT_LABEL
weightLabel.trailingAnchor.constraint(equalTo: weigtImageView.trailingAnchor, constant: -30).isActive = true;
weightLabel.leadingAnchor.constraint(equalTo: weigtImageView.leadingAnchor, constant: 25).isActive = true;
weightLabel.heightAnchor.constraint(equalTo: weigtImageView.heightAnchor, multiplier: 1).isActive = true;
//TEXT_VIEW
textView.topAnchor.constraint(equalTo: weigtImageView.bottomAnchor).isActive = true;
textView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10).isActive = true
textView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10).isActive = true;
textView.heightAnchor.constraint(equalToConstant: 100).isActive = true;
textView.addSubview(nameTextField)
textView.addSubview(tagTextField)
textView.addSubview(setButtonView)
//TAG_CONTROLLER
tagController.heightAnchor.constraint(equalToConstant: 110).isActive = true;
tagController.topAnchor.constraint(equalTo: self.weigtImageView.bottomAnchor).isActive = true;
tagController.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant : 10).isActive = true
tagController.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -10).isActive = true
//SET_BUTTON_VIEW
setButtonView.topAnchor.constraint(equalTo: textView.topAnchor).isActive = true;
setButtonView.bottomAnchor.constraint(equalTo: textView.bottomAnchor).isActive = true;
setButtonView.trailingAnchor.constraint(equalTo: textView.trailingAnchor).isActive = true;
setButtonView.widthAnchor.constraint(equalToConstant: 110).isActive = true;
//NAME_TEXT_FIELD
nameTextField.trailingAnchor.constraint(equalTo: setButtonView.leadingAnchor, constant: -5).isActive = true
nameTextField.leadingAnchor.constraint(equalTo: textView.leadingAnchor, constant: 10).isActive = true
nameTextField.topAnchor.constraint(equalTo: textView.topAnchor, constant: 13).isActive = true
nameTextField.heightAnchor.constraint(equalToConstant: 31).isActive = true
nameTextField.layer.cornerRadius = 8
nameTextField.backgroundColor = .white;
//TAG_TEXT_FIELD
tagTextField.trailingAnchor.constraint(equalTo: setButtonView.leadingAnchor, constant: -5).isActive = true
tagTextField.leadingAnchor.constraint(equalTo: textView.leadingAnchor, constant: 10).isActive = true
tagTextField.bottomAnchor.constraint(equalTo: textView.bottomAnchor, constant: -13).isActive = true
tagTextField.heightAnchor.constraint(equalToConstant: 31).isActive = true
tagTextField.layer.cornerRadius = 8
tagTextField.backgroundColor = .white
here's the viewcontrollers setup:
class UIScaleControllerVew: UIViewController, UITextFieldDelegate, SuggenstionCloudDelegate {
let weigtImageView : UIImageView = {
var imageView = UIImageView(image: UIImage(named: "scaleVisorShadow"));
imageView.contentMode = .scaleToFill
imageView.translatesAutoresizingMaskIntoConstraints = false;
return imageView
}()
let weightLabel : UILabel = {
let label = UILabel()
label.text = "135 gr"
label.font = UIFont(name: "Avenir-Light", size: 50.0)
label.textAlignment = .right
label.translatesAutoresizingMaskIntoConstraints = false
return label
}();
let textView : UIView = {
var view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false;
return view;
}();
let setButtonView : UIImageView = {
var imageView = UIImageView(image: UIImage(named: "setButton"))
imageView.translatesAutoresizingMaskIntoConstraints = false;
return imageView;
}();
let nameTextField : UITextField = {
var textField = UITextField();
textField.tag = 2;
textField.translatesAutoresizingMaskIntoConstraints = false;
textField.addTarget(self, action: #selector(nameFieldEditingChanged(_:)), for: UIControl.Event.editingChanged)
return textField;
}();
let tagTextField : UITextField = {
var textField = UITextField();
textField.tag = 1;
textField.translatesAutoresizingMaskIntoConstraints = false;
textField.addTarget(self, action: #selector(textFieldEditingChanged(_:)), for: UIControl.Event.editingChanged)
return textField;
}();
let bottomMenu : UIView = {
var view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false;
return view;
}();
let saveButton : UIButton = {
let button = UIButton()
button.setImage(UIImage(named: "save"), for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false;
return button
}();
let microPhoneButton : UIButton = {
let button = UIButton()
button.setImage(UIImage(named: "microPhone"), for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false;
return button;
}();
let suggestionCloud : SuggenstionCloud = {
let cloud = SuggenstionCloud(image: UIImage(named: "suggestionCloud.png"))
cloud.translatesAutoresizingMaskIntoConstraints = false;
return cloud;
}();
let tagController : TagController = {
let tagController = TagController()
tagController.translatesAutoresizingMaskIntoConstraints = false
return tagController;
}()
let scaleModel = ScaleModel.init()
override func viewDidLoad() {
super.viewDidLoad()
print("UIScaleController_DidLoad")
tagTextField.delegate = self
nameTextField.delegate = self;
suggestionCloud.delegate = self;
view.backgroundColor = UIColor(hexString: "8ED7F5")
view.addSubview(weigtImageView)
view.addSubview(textView)
view.addSubview(bottomMenu);
view.addSubview(suggestionCloud)
view.addSubview(tagController)
tagController.isHidden = true;
setUpLayout()
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShowNotification(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHideNotification(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil)
}
var didSetUpSuggestionCloud = false
var didSetUpTagController = false
override func viewDidLayoutSubviews() {
guard !self.didSetUpTagController else {
return
}
guard !self.didSetUpSuggestionCloud else {
return
}
self.didSetUpSuggestionCloud = true
self.didSetUpTagController = true
};
and here's the problematic code:
#objc func keyboardWillShowNotification(notification: Notification ) {
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
// collapse and hide bottom view
bottomMenu.contentMode = .scaleToFill;
bottomMenu.heightAnchor.constraint(equalToConstant: 0).isActive = true;
bottomMenu.isHidden = true
// collapse and hide top view
weigtImageView.contentMode = .scaleToFill;
weigtImageView.heightAnchor.constraint(equalToConstant: 0).isActive = true;
weigtImageView.isHidden = true;
// spawn my tag view
tagController.topAnchor.constraint(equalTo: self.textView.bottomAnchor).isActive = true;
tagController.bottomAnchor.constraint(equalTo: suggestionCloud.topAnchor).isActive = true
tagController.isHidden = false;
// set textviews new constraints
textView.bottomAnchor.constraint(equalTo: tagController.topAnchor).isActive = true;
// set middleView's new constraints
suggestionCloud.topAnchor.constraint(equalTo: tagController.bottomAnchor).isActive = true;
suggestionCloud.bottomAnchor.constraint(equalTo: bottomMenu.topAnchor, constant: -keyboardSize.height).isActive = true
self.view.layoutIfNeeded()
}
}
Now there are so many unexpected things happening that i'm positive that my approach to this is just wrong conceptually.
Please let me know where I need to look for a solution.
Here are e few pictures of what is happening so far:
So when the keyboard is up:
The weightView is collapsed: suggestioncloud and text are moved up.
If a tag is added a new view called tagController needs to be places between the texView and the suggesitonCloud. Lastyl the keybaord needs to be collapsed again.
Ill add some sscreenshots
One thing that you could look at is duplicate constraints.
Every time you call weigtImageView.heightAnchor.constraint(equalToConstant: 0).isActive = true you are creating a new constraint. This does not automatically replace any previous height constraints that are active.
To replace a constraint, you need to keep a reference to it, deactivate it, and then activate a new one (and optionally assign it the the variable you use to keep a reference).
Stack views
Stack views could be helpful in your situation, because they automatically collapse views that have isHidden set to true. I think that as long as the direct subviews of the StackView have an intrinsic content size (e.g. correct internal constraints), they should be placed correctly by the StackView.
If you do not have a strong reference to your views to be released then it is enough to execute this:
if view2Breleased.superview != nil {
view2Breleased.removeFromSuperview()
}
The view will then disappear and be released from memory.
If you don't know what a strong reference is then for the time being, just try the code I wrote. The views will disappear anyway.
(Strong reference means you have assigned the view to a variable which survives the execution of the code view2Breleased.removeFromSuperview() and the exit from the function call where the code view2Breleased.removeFromSuperview() is.)
You can change the constant of the heightAnchor constraint like so:
import Foundation
import UIKit
class TestController : UIViewController {
var myViewHeightConstraint : NSLayoutConstraint!
let myView : UIControl = {
let newView = UIControl()
newView.translatesAutoresizingMaskIntoConstraints = false
newView.backgroundColor = .red
return newView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
self.myView.addTarget(self, action: #selector(viewClicked), for: .touchUpInside)
self.myViewHeightConstraint = myView.heightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.heightAnchor)
setup()
}
func setup(){
view.addSubview(myView)
myView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
myView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
myView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
self.myViewHeightConstraint.isActive = true
}
#objc func viewClicked() {
self.myViewHeightConstraint.constant = -self.myView.frame.size.height
}
}
In my example, I set the constant to minus the height of the view's frame height, which effectively collapses it.

Swift 4: Add View on top of all controllers

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)
}

Resources