How to programatically add floating header bar to a UIScrollView - ios

Okay, someone please shed some light here. I am very new to coding, still. Ive been learning swift for about a month or so and have taken the route of designing my first super simple app as my learning path. This gives me purpose that other app builds lacked but is a different learning curve in and of itself.
In this situation I have created almost my entire UI in Xcode Interface Builder using auto-layout and graphics. This creates the situation where there is no code at all for the UI. Great and simple to learn fast however, now I need to do something programmatically (so it seems) and I don't even know where to start.
I have a scrollview, it puts out about 100 lines of calculations, in 9 columns. The view is larger and scrolling vertical and horizontal. I also have a label header describing each column. When the user scrolls down (data up) it is impossible to keep track of which column is which because the label header disappears. I am trying to figure out how to add a floating header to my scrollview just like you would "freeze" a header in spreadsheets.
Thus far I've tried everything I could in Interface Builder, all the searches I've done turn up only programmatic solutions, which is fine except that I have no UI code to attach to...
So I really have one question:
How do you add a floating header in UI Builder OR programmatically when there is no initial code? I want my labels horizontal stackview to sit still as a floating header.
Any help is much appreciated, been racking my head over this the last 3 whole days and its the last change before I'm done my app :(
Edit: here is my code and a few screenshots of initial view, scroll right and scroll down. As you can see there is no UI code really. I want to do something like this but not with a tableView: https://mariusschulz.com/blog/adding-a-fixed-header-to-a-uiscrollview
Even more ideally something like this but not with a table or collectionView... https://imgur.com/a/vwLzy2v
import UIKit
class MetricVerticalViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var shelfHeight: UITextField!
#IBOutlet weak var squeezeFour: UILabel!
#IBOutlet weak var squeezeThree: UILabel!
#IBOutlet weak var squeezeTwo: UILabel!
#IBOutlet weak var squeezeOne: UILabel!
#IBOutlet weak var normalGauge: UILabel!
#IBOutlet weak var gainOne: UILabel!
#IBOutlet weak var gainTwo: UILabel!
#IBOutlet weak var gainThree: UILabel!
#IBOutlet weak var gainFour: UILabel!
override func viewDidLoad() {
shelfHeight.delegate = self
super.viewDidLoad()
}
// Calculate button tapped will cause math to occur and the text field to show the values
#IBAction func calculateSpacing(_ sender: UIButton) {
squeezeFour.text = ""
squeezeThree.text = ""
squeezeTwo.text = ""
squeezeOne.text = ""
normalGauge.text = ""
gainOne.text = ""
gainTwo.text = ""
gainThree.text = ""
gainFour.text = ""
// Resigns the keyboard when the button is pressed
self.view.endEditing(true)
calculateSpacing()
}
// Acts to dismiss number keyboard when user taps outside
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
brickHeight.resignFirstResponder()
}
func calculateSpacing() {
let shelfHeight = Int(shelfHeight.text!) ?? 1
let squeezeFourGauge = shelfHeight + 6
let squeezeThreeGauge = shelfHeight + 7
let squeezeTwoGauge = shelfHeight + 8
let squeezeOneGauge = shelfHeight + 9
let normalJoint = shelfHeight + 10
let gainOneGauge = shelfHeight + 11
let gainTwoGauge = shelfHeight + 12
let gainThreeGauge = shelfHeight + 13
let gainFourGauge = shelfHeight + 14
for index in 1...100 {
squeezeFour.text! += "Shelf \(index) = \(index * squeezeFourGauge)\n"
squeezeThree.text! += "Shelf \(index) = \(index * squeezeThreeGauge)\n"
squeezeTwo.text! += "Shelf \(index) = \(index * squeezeTwoGauge)\n"
squeezeOne.text! += "Shelf \(index) = \(index * squeezeOneGauge)\n"
normalGauge.text! += "Shelf \(index) = \(index * normalJoint)\n"
gainOne.text! += "Shelf \(index) = \(index * gainOneGauge)\n"
gainTwo.text! += "Shelf \(index) = \(index * gainTwoGauge)\n"
gainThree.text! += "Shelf \(index) = \(index * gainThreeGauge)\n"
gainFour.text! += "Shelf \(index) = \(index * gainFourGauge)\n"
}
}
extension ViewController : UITextFieldDelegate {
// Acts to dismiss keyboard once user presses return (possibly unnecessary as this was used for text keyboard input in original code)
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
}

One way to access Interface Builder items from code is via an outlet connection. You have to open your code next to Interface Builder, then control-drag the item into your view controller code and make sure to choose "Outlet" as the connection type. See Apple's tutorial for more info.

So I did a little work and was actually able to create a floating header of the sizes in the above screenshots by adding a second view to the scrollview and constraining it like crazy. It worked great in the iPhone 11 Pro Max size because it was constrained using that size but then every other device was screwed up. I will try and set "vary for traits" constraints for other sizes and see if its a stable workaround.
If it works again i'll post some kind of write-up.
Apparently its bad to have more than one view inside a scrollView... worked okay for me.
Anyone else have any better solutions to this?

Related

How can I listen for UILabel content changes?

I want to listen the content changes in the UIlabel and prevent it from changing when its length is greater than 7. What should I do? I tried set / get / willset / didset. It seems that it can't meet my needs
I wrote a simple demo ,When I press the button, add a number to display area. I don't want his length to exceed my expectations
In my real project, I'm developing a calculator
What I can think of is to judge the length of displayValue, but doing so will make my code wordy
didSet can help you better.
add this code in line number 6 in your code.
var displayValue: String = "" {
didSet {
if displayValue.count <= 7 {
customLabel.text = displayValue
}
}
}
Then in your action function, you need to just do it.
#IBAction func clickBtn( _ sender: Any) {
displayValue = displayValue + "0"
}
For counting the characters inside a label you use the count method in the text property like this
#IBOutlet weak var testLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
testLabel.text = "duck"
print(testLabel.text.count) //return 4
}
For user action you can't use the UILabel since it's not user interactive, you have to use the UITextField and connect to him an UIAction or explain us better what you want to do.

UIAccessibility announcing n of n elements on custom views

When you add a UISegmentedControl to a view, UIAccessibility will focus on it and say:
"(Selected) ItemName Button 1 of 2"
"ItemName Button 2 of 2"
I have a custom control that has UIButtons that toggle similar to a UISegmentedControl. But what I'm trying to figure out is how to get the Voice Over to announce the n of n at the end.
The closest thing that I've found is assigning the .accessibilityTraits = .tabBar on the container. The issue is that it announces:
"ItemName Button Tab 2 of 2"
But to conform to our accessibility guidelines we can't have it announce "tab".
https://developer.apple.com/documentation/uikit/uiaccessibility/uiaccessibilitytraits/1648592-tabbar
Short of just writing a custom accessibilityLabel is there anything within UIAccessibility that can handle this logic?
Set the container view's accessibilityTraits = .tabBar
Set the container view's accessibilityElements = [button1, button2]
Set each button's accessibilityTraits = .selected or .none when necessary
I have a custom control that has UIButtons that toggle similar to a UISegmentedControl. But what I'm trying to figure out is how to get the Voice Over to announce the n of n at the end.
Put each one of your UIButton elements in the accessibilityElements array of the custom control that acts like a container.
By searching a specific element in this array, you will have an index 'x' inside a total amount 'N' of buttons: "item name button x of N".
In your UIButton, set accessibilityLabel by inserting the result of the previous research.
Here's a kind of logic that should help you reach your purpose with a little bit of code as follows for instance (Xcode 10.2.1, Swift 5.0, iOS 12):
class ButtonsViewController: UIViewController {
#IBOutlet weak var myCustomContainer: UIView!
#IBOutlet weak var btn1: UIButton!
#IBOutlet weak var btn2: UIButton!
#IBOutlet weak var btn3: UIButton!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
myCustomContainer.accessibilityElements = [btn1!, btn2!, btn3!]
let nbButtons = myCustomContainer.accessibilityElements?.count
for (index, elt) in (myCustomContainer.accessibilityElements?.enumerated())! {
let btn = elt as! UIButton
let btnName = btn.titleLabel?.text
btn.accessibilityLabel = btnName! + String(index + 1) + " of " + String(describing: nbButtons!)
}
}
}

Sum of three textfield value without clicking a button (Swift - Xcode)

I'm currently having problems for my label to read the addition of 3 textfield values automatically, without a button function action. As such i only want my textfield to be an Int input only. There's a screenshot attached below for better reference. Appreciate those who can help me with this. Thanks!
ViewController
import UIKit
class TryingoutController: UIViewController {
#IBOutlet var impact: UITextField!
#IBOutlet var rigour: UITextField!
#IBOutlet var response: UITextField!
#IBOutlet var total: UILabel!
One way is to add self as the target to the text fields, for the control event .editingChanged
impact.addTarget(self, action: #selector(textChanged), for: .editingChanged)
// do the same for other textfields
Then declare a textChanged method. This should handle what happens when the texts in the text fields change. One implementation would be to add up all the values in the text fields (if any, and is valid) and display it in the label.
func textChanged() {
let impactValue = Int(impact.text!)
let rigourValue = Int(rigour.text!)
let responseValue = Int(response.text!)
total.text = String(describing:
(impactValue ?? 0) + (rigourValue ?? 0) + (responseValue ?? 0)
)
}
Optionally, you can conform to UITextFieldDelegate:
class TryingoutController: UIViewController, UITextFieldDelegate {
}
and implement shouldChange according to this answer by Thuggish Nuggets. Then, set the delegates of the text fields to self:
impact.delegate = self
// do the same for other text fields.

How to print by press of a button a simple quote in swift at random from an array?

I am completely new to coding.
I started using this app called MIMO and learned a few bits and pieces. In one chapter they let us code a simple "dice app" where by pressing a button a number from 1 - 6 appears. Now I wanted to rewrite that so that at the button press the app displays a quote from a predetermined array.
I am completely stuck, however.
Here's what I got:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var quotesLabel: UILabel!
let quotes = ["Quote1!", "Quote2!"]
let randomIndex = Int(arc4random_uniform(UInt32(quotes.count)))
let randomQuote = quotes[randomIndex]
print(array[randomIndex])
override func viewDidLoad() {
super.viewDidLoad()
}
}
Basically any arbitrary code must be run in a method, in this case in an IBAction which is triggered when the button is pressed. The method viewDidLoad is not needed.
Change the code to
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var quotesLabel: UILabel!
let quotes = ["Quote1!", "Quote2!", "Quote3!", "Quote4!"]
#IBAction func showRandomQuote(_ sender : UIButton) {
let randomIndex = Int(arc4random_uniform(UInt32(quotes.count)))
let randomQuote = quotes[randomIndex]
quotesLabel.text = randomQuote
}
}
In Interface Builder drag a button into the canvas of the view controller
Connect (⌃-drag) the button to the IBAction and the label to the IBOutlet
Run the app and press the button

Adding spaces in front of text field via subclassing

So I am currently trying to add some space before my text in a text field. So when I type, it starts with four spaces instead of none. So I did some research and I came across something that seems really good (as indicated by all the votes), i.e., Create space at the beginning of a UITextField. One problem is that I do not know how to actually implement this in my other classes (assuming that's what the post is intending the reader to do).
This is what I think I'm supposed to do. I think I'm supposed to instantiate an object of that class and use the methods in the class to add spaces in front of my text field. But I don't actually know what that looks like in code. Could anyone give me an example of how to actually implement the code on this post? Create space at the beginning of a UITextField
Here is the code that I have so far:
import UIKit
class SignUpViewController: UIViewController {
#IBOutlet weak var facebookButton: UIButton!
#IBOutlet weak var googleplusButton: UIButton!
#IBOutlet weak var fullNameTextField: UITextField!
#IBOutlet weak var emailAddressTextField: UITextField!
#IBOutlet weak var passwordTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
facebookButton.layer.cornerRadius = 5
googleplusButton.layer.cornerRadius = 5
fullNameTextField.layer.borderColor = UIColor.lightGrayColor().CGColor
fullNameTextField.layer.borderWidth = 1.0
emailAddressTextField.layer.borderColor = UIColor.lightGrayColor().CGColor
emailAddressTextField.layer.borderWidth = 1.0
passwordTextField.layer.borderColor = UIColor.lightGrayColor().CGColor
passwordTextField.layer.borderWidth = 1.0
}
}
You need to create subclass of UITextField. Then add this code in its implementation
static CGFloat leftMargin = 10;
- (CGRect)textRectForBounds:(CGRect)bounds
{
bounds.origin.x += leftMargin;
bounds.size.width -= (leftMargin + 20);
return bounds;
}
- (CGRect)editingRectForBounds:(CGRect)bounds
{
bounds.origin.x += leftMargin;
bounds.size.width -= (leftMargin + 20);
return bounds;
}
After that, set custom class to your UITextField.
You should create a class from #ScareCrow answer. After that go to the storyboard and change the class of the UITextField. Such as:
After that create an IBOutlet of the textfield. That outlet will be instance of the TextField class.

Resources