Height of view changes upon calculation? - swift

Swift 3
I have a view with some labels in it that I'm animating into the field of view when a user taps a button. I've used this identical code in another view controller and it worked just fine. For some reason, though, I can't get it to work now. It's always off by 1.5 pixels. Really weird.
This is what is supposed to happen: The scene loads, and the view is hidden to start. The user taps the "show details" button, and the view slides down into view.
But what happens is this: The scene loads and the view is hidden. So far so good. The user taps the "show details button", and the button doesn't do anything. It just slides down a negligible amount. Then the user taps it again, and it finally reveals the view. Why the two taps? I don't know.
I had the console print out the height of the view, and when the scene first loads, the view is at 237.0 for height, but when I tap the button, the height becomes 235.5. Why? This is really puzzling me.
Here's the code:
class Step5IncomeSummaryVC: UIViewController {
#IBOutlet weak var detailsView: UIView!
#IBOutlet weak var viewTop: NSLayoutConstraint!
#IBOutlet weak var showDetailsButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
showDetailsButton.setTitle("show details", for: .normal)
viewTop.constant = -(detailsView.bounds.height)
detailsView.isHidden = true
print("Initial view height: \(detailsView.bounds.height)") // check height of view BEFORE animation
}
#IBAction func showDetailsButtonTapped(_ sender: UIButton) {
print("Button pressed view height: \(detailsView.bounds.height)") // check height of view AFTER button press
if viewTop.constant == -(detailsView.bounds.height) {
detailsView.isHidden = false
showDetailsButton.setTitle("hide details", for: .normal)
UIView.animate(withDuration: 0.25, animations: {
self.viewTop.constant = 0
self.view.layoutIfNeeded()
})
print("After animation view height: \(detailsView.bounds.height)") // check height of view after animation
} else {
UIView.animate(withDuration: 0.25, animations: {
self.viewTop.constant = -(self.detailsView.bounds.height)
self.view.layoutIfNeeded()
})
showDetailsButton.setTitle("show details", for: .normal)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
self.detailsView.isHidden = true
}
}
}

Related

Swift iPad - How to present a formSheet with custom size based on a table view content?

In my app i present a form sheet in this way for iPad devices:
myViewController = .formSheet
present(myViewController, animated: true)
This ViewController is very simple: there is just a table view displaying n elements.
My goal is make the formSheet size the same as the table view with its n elements.
I actually achieved that with this code:
class MyViewControlller: UIViewController {
#IBOutlet weak var myTable: UITableView!
private var observer: NSKeyValueObservation?
override func viewDidLoad() {
super.viewDidLoad()
myTable.delegate = self
myTable.dataSource = self
observer = myTable(\.contentSize, options: [.new], changeHandler: { [weak self] (table, _) in
guard let self = self else {
return
}
self.preferredContentSize = CGSize(width: table.contentSize.width, height: table.contentSize.height + 30.0)
self.view.layoutIfNeeded()
})
}
}
But the problem is that while presenting the formSheet (while it is sliding from bottom to the center of the screen) I can clearly see an animation where the bottom of the sheet is adjusting its size until it reaches the center of the screen.
What I'm trying to achieve is this: When the sheet starts sliding from the bottom to the center of the screen it already has the right dimensions.
Am I doing something wrong with the code?

button reversed when pressed

Would like to create a button "scan" that is reversed and outlined when pressed (see image below)
Was using a button configurator (with storyboard) but am having trouble getting background color to update with isHighlighted. Happy to switch to SwiftUI from Storyboard if necessary.
#IBOutlet var imageView: UIImageView!
let scanButton = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .black
configButton()
scanButton.configurationUpdateHandler = { button in
//this transform is for testing and works
button.transform = button.isHighlighted ? CGAffineTransform(scaleX: 0.95, y: 0.95) : .identity
var config = button.configuration
//changing the title works
config?.title = button.isHighlighted ? "pressed" : "scan"
//config?.background = button.isHighlighted ? //need some help
button.configuration = config
}
}
func configButton(){
scanButton.configuration = .filled()
scanButton.configuration?.baseBackgroundColor = .green
scanButton.configuration?.title = "scan"
scanButton.configuration?.baseForegroundColor = .black
addButtonConstraints()
}
func addButtonConstraints(){
view.addSubview(scanButton)
scanButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
scanButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
scanButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),
scanButton.widthAnchor.constraint(equalToConstant: 125),
scanButton.heightAnchor.constraint(equalToConstant: 30)
])
}
Does this work for you?
The button in this example was placed using Interface Builder as a Filled Button in Main.storyboard, then connected to the View Controller code with an #IBOutlet. The code example is ViewController.swift.
The button inverts its color scheme and gains a contrasting outline when it is pressed down, then reverts to its original appearance when it is released.
import UIKit
class ViewController: UIViewController {
// Set up this outlet to allow accessing the button's layer and configuration properties
#IBOutlet weak var swapColorsButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Set up the button's outline. Since the button has a white background when not pressed down, the outline does not become visible until the button is pressed.
swapColorsButton.layer.borderWidth = 3
swapColorsButton.layer.borderColor = CGColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
swapColorsButton.layer.cornerRadius = 5
}
// This is a Touch Down action -- it inverts the button's colors when it is pressed
#IBAction func pressDown(_ sender: UIButton) {
swapColorsButton.configuration?.baseBackgroundColor = .black
swapColorsButton.configuration?.baseForegroundColor = .white
}
// This is a Touch Up Inside action -- releasing the button resets it to its original colors
#IBAction func buttonReleased(_ sender: UIButton) {
swapColorsButton.configuration?.baseBackgroundColor = .white
swapColorsButton.configuration?.baseForegroundColor = .black
}
}
In order to detect being pressed down and released without using the isHighlighted property, the example code has two separate #IBActions controlling the color changes. Pressing the button down is a Touch Down action, and releasing the button is Touch Up Inside. To get the full menu of #IBAction options, instead of just the default one, right click on the button in Main.storyboard. You can then drag and drop from the menu options to ViewController.swift.

Changing constraints by pressing a button

Is there a way to change view’s centerXAnchor constraint to another object (with animation) by pressing a button while the app is running?
Thank's for all of the answers!
First, you need to create an IBOutlet for your constraint from the storyboard, something similar to this.
#IBOutlet weak var centerXConstraint: NSLayoutConstraint!
And when the button is pressed, you should change the constraint value and update the view layout.
centerXConstraint.constant = setYourValueHere
UIView.animate(withDuration: 0.3, animations: {
self.view.layoutIfNeeded()
})
If you want to add constraints programmatically, then remove #IBOutlet weak.
var centerXConstraint: NSLayoutConstraint!
Assign your view's center X anchor to it.
centerXConstraint = yourView.centerXAnchor.constraint(equalTo: super.view.centerXAnchor, constant: 0)
centerXConstraint.isActive = true
after that, you can change it as described above.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var centerXConstraintOutlet: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func buttonClickAction(_ sender: UIButton) {
animateViewCenterXConstraint()
}
func animateViewCenterXConstraint() {
UIView.animate(withDuration: 0.3) {
self.centerXConstraintOutlet.constant = 30
self.view.layoutIfNeeded()
}
}
}
1) Create view’s centerXAnchor constraints related to both the objects.
2) Take outlets of both the constraints.
3) Activate and de-activate them on click of the button as you wish.
Declare variable for centerXAnchor constraint and assign value on button action. You can try this:
var constraintOfCenterXAnchor: NSLayoutConstraint = NSLayoutConstraint()
in viewDidLoad() set constraintOfCenterXAnchor.constant = 0
When button pressed, change constraint value and update view
constraintOfCenterXAnchor.constant = 30
UIView.animate(withDuration: 0.3, animations: {
self.view.layoutIfNeeded()
})

How to get textFields from static cells in UITableViewController? Swift

My View hierarchy looks like this:
ElevethViewController of type UIViewController
Container View
ManagedTableEleventhViewController of type UITableViewController embedded in Container View
ManagedTableEleventhViewController contains 4 static cells containing 1 textField each and one empty static cell.
class ManagedTableEleventhViewController: UITableViewController,UITextFieldDelegate {
var hasText:Bool!
#IBOutlet weak var fullName: UITextField!
#IBOutlet weak var flatNumber: UITextField!
#IBOutlet weak var streetAddress: UITextField!
#IBOutlet weak var phoneNumber: UITextField!
//checkValue takes ELViewController parameter so that segue can be
//performed when button is touched in EleventhViewController
func checkValue(ELViewController:EleventhViewController) {
//loop through the textfields and check if they have text
for case let textField as UITextField in viewController.view.subviews {
//print is not executed meaning loop is not performed
print("some text")
if textField.text == "" {
self.hasText = false
textField.layer.borderColor = UIColor.red.cgColor
} else {
print("true value in for loop")
self.hasText = true
performSegue(withIdentifier: "elevethToTwelveth", sender: ELViewController)
}
}//end of for loop
}
class EleventhViewController: UIViewController {
var nextButtonOutlet:UIButton!
override func viewDidLoad() {
super.viewDidLoad()
//create button programmatically
var button = UIButton(type: UIButtonType.custom) as UIButton
button = UIButton(frame: CGRect(x: 0, y: 637, width: 375, height: 50))
button.titleLabel?.textColor = UIColor.white
button.backgroundColor = UIColor(colorLiteralRed: 117/255, green: 232/255, blue: 0, alpha: 1)
button.setTitle("Next", for: .normal)
button.addTarget(self, action: #selector(EleventhViewController.nextButton), for: .touchUpInside)
self.view.addSubview(button)
self.nextButtonOutlet = button
}
func nextButton(sender: UIButton) {
//create instance of tableView
let managedTable = ManagedTableEleventhViewController()
managedTable.checkValue(viewController: self)
} //end of EleventhViewController class
Well first I can give you an answer that might satisfy you and fix your loop but I would recommend not doing it that way to alter your textfields. I would recommend doing it in cellForRow even though they may be static cells. Depending on your view setup in the cells it would look like this if the textfield is added directly to the cells and not to another view.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("Testing")
for cell in tableView.visibleCells{
for sub in cell.contentView.subviews{
if sub is UITextField{
print("Textfield")
}
}
}
}
Just to follow up, if this is for validation you should'nt be only checking "" case, because you allow " ", " " etc. Use isEmpty, it should work better, if you only want to check existence of text.
Also you dont have to extract fields from subviews as you already have properties, i'm not sure if you have any other reason for this logic though.
Edit. Ooops, i just noticed your checking for textfields in a controller which does not have any visible fields, so normally your check never passes.
I think you should'nt even validate textfields for one class in another class, unless its a class handling textfield validation in general.
In EleventhViewController you have no textfields, so nothing to validate.

Swift - changing app when banner loads. - xcode 6 GM

I have been working on a mainly text-based application with an Iadbanner in the bottom.
However as we all know, Iads aren't always there. So I would like to be able to dynamically update the height of my textView, so when the banner is hidden, the textView takes up the wasted space. And resize it when a banner is loaded.
Here's what I currently have for the Viewcontroller in question
import UIKit
import iAd
class DetailVC: UIViewController, UITextViewDelegate, ADBannerViewDelegate {
//Our label for displaying var "Items/cellName"
#IBOutlet var imageViewOutlet: UIImageView!
//connect in IB connection inspector with your ADBannerView
#IBOutlet var adBannerView: ADBannerView!
//Receiving variable assigned to our mainVC var "items"
var cellName: String = ""
var imageView: UIImageView = UIImageView()
var image = UIImage(named: "handcuffs.png")
var textViewText: String = ""
var textView: UITextView = UITextView(frame: CGRect(x: 5.0, y: 238.0, width: 315.00, height: 283.00))
//height = 332 for full screen 283 for small
override func viewDidLoad() {
super.viewDidLoad()
println("+--------------------+")
println("| Detail view loaded |")
println("+--------------------+")
// Iad stuff
self.adBannerView.delegate = self
self.canDisplayBannerAds = true
self.adBannerView.hidden = true //hide until ad loaded
//Setting up the textView
textView.text = textViewText
textView.editable = false
textView.backgroundColor = UIColor.clearColor()
textView.font = UIFont(name: "Helvetica", size: 15)
//adding textview as subview
self.view.addSubview(textView)
//ImageViewOutlets
imageViewOutlet.image = image
//Assign your string var to your navbar title
self.title = cellName
func bannerViewWillLoadAd(banner: ADBannerView!) {
NSLog("bannerViewWillLoadAd")
}
func bannerViewDidLoadAd(banner: ADBannerView!) {
NSLog("bannerViewDidLoadAd")
//self.textView.removeFromSuperview()
//textView = UITextView(frame: CGRect(x: 0.0, y: 238.0, width: 320.00, height: 283.00))
//self.view.addSubview(textView)
self.adBannerView.hidden = false //now show banner as ad is loaded
}
func bannerViewActionDidFinish(banner: ADBannerView!) {
NSLog("bannerViewActionDidFinish")
//optional resume paused app code
}
func bannerViewActionShouldBegin(banner: ADBannerView!, willLeaveApplication willLeave: Bool) -> Bool {
NSLog("bannerViewActionShouldBegin")
//optional pause app code
return true
}
func bannerView(banner: ADBannerView!, didFailToReceiveAdWithError error: NSError!) {
NSLog("didFailToReceiveAdWithError")
}
//... your class implementation code
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
The commented out code in the function "bannerViewDidLoadAd" is what I thought would have fixed my issue. Sadly that function seems to never run? I'm not very familiar with Iads so hopefully someone out there can give me a hint as to how to change the height of a textView when an ad loads.
CanDisplayBannerAds :
Set this to enable automatic management of banner ad display with the view controller. It's important to note that this will modify the view hierarchy of the view controller by inserting a new container view above the view controller's view. The impact is that the view controller's view property will no longer return the originally provided view, it will return the new container. To access the original view, use the originalContentView property.
So, remove this line :
self.canDisplayBannerAds = true
Everything should work.