UISlider not updating values - swift

apologies if this is a stupid question. I can't seem to get my slider to update its value as its being interacted with. (I'm going to point everyone to the very last method in this long code)
class CustomSlider: UISlider {
override func trackRect(forBounds bounds: CGRect) -> CGRect {
var rect = super.trackRect(forBounds: bounds)
rect.size.height = 7
return rect
}
}
class FactionButton: CustomSlider {
var factionSlider = CustomSlider(frame: CGRect(x: 15, y: 542, width: 386, height: 57))
func factionBalanceSlider(){
factionSlider.minimumValueImage = #imageLiteral(resourceName: "Alliance Slider")
factionSlider.maximumValueImage = #imageLiteral(resourceName: "Horde Slider")
factionSlider.setThumbImage(#imageLiteral(resourceName: "Thumb Image"), for: .normal)
factionSlider.minimumTrackTintColor = UIColor(red:0.08, green:0.33, blue:0.69, alpha:0.8)
factionSlider.maximumTrackTintColor = UIColor(red:1.00, green:0.00, blue:0.00, alpha:0.59)
factionSlider.setValue(0.5, animated: true)
factionSlider.isContinuous = false
factionSlider.addTarget(self, action: #selector(recordFactionBalance(sender:)), for: .valueChanged)
}
func getSlider() -> CustomSlider {
return factionSlider
}
override func trackRect(forBounds bounds: CGRect) -> CGRect {
var customBounds = super.trackRect(forBounds: bounds)
customBounds.size.height = 10
return customBounds
}
#objc func recordFactionBalance(sender: CustomSlider){
//also calculates balance and adds it into the quiz data
print("hi")
print(sender.value) //It's this part that doesn't work
}
}
It's this bit nearest to the bottom that has the issue. (Everything else is fine) The action function doesn't seem to be triggered at all, even when I'm interacting with it. Neither print statements are being executed. Any ideas why?
Cheers

From the getSlider(), i can guess you are using this class as a utility to get the CustomSlider. So, i suspect you are adding the slider to the view as below,
let container = FactionButton()
container.factionBalanceSlider()
let slider = container.getSlider()
self.view.addSubview(slider)
If you will not add the container to the view which is set as the receiver for .valueChange event so it will not get any event. To receive events you also need to add the container in the view as below,
self.view.addSubview(container)

Related

Programmatically emptying UIStackView

I have a fairly simple code which, upon clicking a button, adds a randomly colored UIView to a UIStackView, and upon a different button click, removes a random UIView from the UIStackView.
Here's the code:
import UIKit
class ViewController: UIViewController, Storyboarded {
weak var coordinator: MainCoordinator?
#IBOutlet weak var stackView: UIStackView!
var tags: [Int] = []
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func buttonPressed(_ sender: UIButton) {
switch sender.tag {
case 10:
let view = UIView(frame: CGRect(x: 0, y: 0, width: stackView.frame.width, height: 20))
var number = Int.random(in: 0...10000)
while tags.contains(number) {
number = Int.random(in: 0...10000)
}
tags.append(number)
view.tag = number
view.backgroundColor = .random()
stackView.addArrangedSubview(view)
case 20:
if tags.count == 0 {
print("Empty")
return
}
let index = Int.random(in: 0...tags.count - 1)
let tag = tags[index]
tags.remove(at: index)
if let view = stackView.arrangedSubviews.first(where: { $0.tag == tag }) {
stackView.removeArrangedSubview(view)
}
default:
break
}
}
}
extension CGFloat {
static func random() -> CGFloat {
return CGFloat(arc4random()) / CGFloat(UInt32.max)
}
}
extension UIColor {
static func random() -> UIColor {
return UIColor(
red: .random(),
green: .random(),
blue: .random(),
alpha: 1.0
)
}
}
I'm not using removeFromSuperview on purpose - since I would (later) want to reuse those removed UIViews, and that is why I'm using removeArrangedSubview.
The issue I'm facing is:
All UIViews are removed as expected (visually of course, I know they're still in the memory) until I reach the last one - which, even though was removed, still appears and filling the entire UIStackView.
What am I missing here?
You can understand removeArrangedSubview is for removing constraints that were assigned to the subview. Subviews are still in memory and also still inside the parent view.
To achieve your purpose, you can define an array as your view controller's property, to hold those subviews, then use removeFromSuperview.
Or use .isHidden property on any subview you need to keep it in memory rather than removing its contraints. You will see the stackview do magical things to all of its subviews.
let subview = UIView()
stackView.addArrangedSubview(subview)
func didTapButton(sender: UIButton) {
subview.isHidden.toggle()
}
Last, addArrangedSubview will do two things: add the view to superview if it's not in superview's hierachy and add contraints for it.

Identifying Objects in Firebase PreBuilt UI in Swift

FirebaseUI has a nice pre-buit UI for Swift. I'm trying to position an image view above the login buttons on the bottom. In the example below, the imageView is the "Hackathon" logo. Any logo should be able to show in this, if it's called "logo", since this shows the image as aspectFit.
According to the Firebase docs page:
https://firebase.google.com/docs/auth/ios/firebaseui
You can customize the signin screen with this function:
func authPickerViewController(forAuthUI authUI: FUIAuth) -> FUIAuthPickerViewController {
return FUICustomAuthPickerViewController(nibName: "FUICustomAuthPickerViewController",
bundle: Bundle.main,
authUI: authUI)
}
Using this code & poking around with subviews in the debuggers, I've been able to identify and color code views in the image below. Unfortunately, I don't think that the "true" size of these subview frames is set until the view controller presents, so trying to access the frame size inside these functions won't give me dimensions that I can use for creating a new imageView to hold a log. Plus accessing the views with hard-coded index values like I've done below, seems like a pretty bad idea, esp. given that Google has already changed the Pre-Built UI once, adding a scroll view & breaking the code of anyone who set the pre-built UI's background color.
func authPickerViewController(forAuthUI authUI: FUIAuth) -> FUIAuthPickerViewController {
// Create an instance of the FirebaseAuth login view controller
let loginViewController = FUIAuthPickerViewController(authUI: authUI)
// Set background color to white
loginViewController.view.backgroundColor = UIColor.white
loginViewController.view.subviews[0].backgroundColor = UIColor.blue
loginViewController.view.subviews[0].subviews[0].backgroundColor = UIColor.red
loginViewController.view.subviews[0].subviews[0].tag = 999
return loginViewController
}
I did get this to work by adding a tag (999), then in the completion handler when presenting the loginViewController I hunt down tag 999 and call a function to add an imageView with a logo:
present(loginViewController, animated: true) {
if let foundView = loginViewController.view.viewWithTag(999) {
let height = foundView.frame.height
print("FOUND HEIGHT: \(height)")
self.addLogo(loginViewController: loginViewController, height: height)
}
}
func addLogo(loginViewController: UINavigationController, height: CGFloat) {
let logoFrame = CGRect(x: 0 + logoInsets, y: self.view.safeAreaInsets.top + logoInsets, width: loginViewController.view.frame.width - (logoInsets * 2), height: self.view.frame.height - height - (logoInsets * 2))
// Create the UIImageView using the frame created above & add the "logo" image
let logoImageView = UIImageView(frame: logoFrame)
logoImageView.image = UIImage(named: "logo")
logoImageView.contentMode = .scaleAspectFit // Set imageView to Aspect Fit
// loginViewController.view.addSubview(logoImageView) // Add ImageView to the login controller's main view
loginViewController.view.addSubview(logoImageView)
}
But again, this doesn't seem safe. Is there a "safe" way to deconstruct this UI to identify the size of this button box at the bottom of the view controller (this size will vary if there are multiple login methods supported, such as Facebook, Apple, E-mail)? If I can do that in a way that avoids the hard-coding approach, above, then I think I can reliably use the dimensions of this button box to determine how much space is left in the rest of the view controller when adding an appropriately sized ImageView. Thanks!
John
This should address the issue - allowing a logo to be reliably placed above the prebuilt UI login buttons buttons + avoiding hard-coding the index values or subview locations. It should also allow for properly setting background color (also complicated when Firebase added the scroll view + login button subview).
To use: Create a subclass of FUIAuthDelegate to hold a custom view controller for the prebuilt Firebase UI.
The code will show the logo at full screen behind the buttons if there isn't a scroll view or if the class's private constant fullScreenLogo is set to false.
If both of these conditions aren't meant, the logo will show inset taking into account the class's private logoInsets constant and the safeAreaInsets. The scrollView views are set to clear so that a background image can be set, as well via the private let backgroundColor.
Call it in any signIn function you might have, after setting authUI.providers. Call would be something like this:
let loginViewController = CustomLoginScreen(authUI: authUI!)
let loginNavigationController = UINavigationController(rootViewController: loginViewController)
loginNavigationController.modalPresentationStyle = .fullScreen
present(loginNavigationController, animated: true, completion: nil)
And here's one version of the subclass:
class CustomLoginScreen: FUIAuthPickerViewController {
private var fullScreenLogo = false // false if you want logo just above login buttons
private var viewContainsButton = false
private var buttonViewHeight: CGFloat = 0.0
private let logoInsets: CGFloat = 16
private let backgroundColor = UIColor.white
private var scrollView: UIScrollView?
private var viewContainingButton: UIView?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// set color of scrollView and Button view inside scrollView to clear in viewWillAppear to avoid a "color flash" when the pre-built login UI first appears
self.view.backgroundColor = UIColor.white
guard let foundScrollView = returnScrollView() else {
print("😡 Couldn't get a scrollView.")
return
}
scrollView = foundScrollView
scrollView!.backgroundColor = UIColor.clear
guard let foundViewContainingButton = returnButtonView() else {
print("😡 No views in the scrollView contain buttons.")
return
}
viewContainingButton = foundViewContainingButton
viewContainingButton!.backgroundColor = UIColor.clear
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Create the UIImageView at full screen, considering logoInsets + safeAreaInsets
let x = logoInsets
let y = view.safeAreaInsets.top + logoInsets
let width = view.frame.width - (logoInsets * 2)
let height = view.frame.height - (view.safeAreaInsets.top + view.safeAreaInsets.bottom + (logoInsets * 2))
var frame = CGRect(x: x, y: y, width: width, height: height)
let logoImageView = UIImageView(frame: frame)
logoImageView.image = UIImage(named: "logo")
logoImageView.contentMode = .scaleAspectFit // Set imageView to Aspect Fit
logoImageView.alpha = 0.0
// Only proceed with customizing the pre-built UI if you found a scrollView or you don't want a full-screen logo.
guard scrollView != nil && !fullScreenLogo else {
print("No scrollView found.")
UIView.animate(withDuration: 0.25, animations: {logoImageView.alpha = 1.0})
self.view.addSubview(logoImageView)
self.view.sendSubviewToBack(logoImageView) // otherwise logo is on top of buttons
return
}
// update the logoImageView's frame height to subtract the height of the subview containing buttons. This way the buttons won't be on top of the logoImageView
frame = CGRect(x: x, y: y, width: width, height: height - (viewContainingButton?.frame.height ?? 0.0))
logoImageView.frame = frame
self.view.addSubview(logoImageView)
UIView.animate(withDuration: 0.25, animations: {logoImageView.alpha = 1.0})
}
private func returnScrollView() -> UIScrollView? {
var scrollViewToReturn: UIScrollView?
if self.view.subviews.count > 0 {
for subview in self.view.subviews {
if subview is UIScrollView {
scrollViewToReturn = subview as? UIScrollView
}
}
}
return scrollViewToReturn
}
private func returnButtonView() -> UIView? {
var viewContainingButton: UIView?
for view in scrollView!.subviews {
viewHasButton(view)
if viewContainsButton {
viewContainingButton = view
break
}
}
return viewContainingButton
}
private func viewHasButton(_ view: UIView) {
if view is UIButton {
viewContainsButton = true
} else if view.subviews.count > 0 {
view.subviews.forEach({viewHasButton($0)})
}
}
}
Hope this helps any who have been frustrated trying to configure the Firebase pre-built UI in Swift.

resignFirstResponder is not working as expected.the key board pop's down for every key press rather than for return only

i have 3 UITextField. when i set the resignFirstResponder for the text field, for every key press the key board goes down and key board pops up when we enter. which means for each letter pressed the key board disappears
I tried creating an outlet for the text field as below but the print statement is executed but the key board is not getting disappeared when focus lost or moved to the next text field
#IBAction func done(_ sender: UITextField) {
print("Text field done$$$$$$$$$$$$$$$")
sender.endEditing(true)
sender.resignFirstResponder()
// print("After Resign")
}
tried the below one also:
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self.view, action: #selector(UIView.endEditing(_:))))
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
//Textfield
let textfield = UITextField()
textfield.setShadowTextField(username, "user-name")
textfield.setShadowTextField(useremail, "email")
textfield.setShadowTextField(userPhone, "phone")
//Hide Keyboard
textName.resignFirstResponder()
textEmail.resignFirstResponder()
userPhone.resignFirstResponder()
//
name = textName.text ?? ""
emailphone = textEmail.text ?? ""
//Button
if isFirstTimeSubView == true {
button?.setSemiButtonLeft(btnFemaleSelector, shadowViewFemale)
button?.setGradientButton(btnFemaleSelector, startColor: "0d5e90", endColor: "8ec67d")
button?.setSemiButtonRight(btnMaleSelector, shadowViewMale)
button?.setGradientButton(btnMaleSelector, startColor: "FFFFFF", endColor: "FFFFFF")
isFirstTimeSubView = false
}
button?.setRoundButton(btnSubmit, shadowView)
button?.setGradientButton(btnSubmit, startColor: "0d5e90", endColor: "8ec67d")
//textNam.sendAction("resignFirstResponder", to:nil, from:nil, forEvent:nil)
// self.userText.delegate = selftextName.resignFirstResponder()
}
extension UITextField {
func setShadowTextField(_ textfield: UITextField?, _ imagename: String?) {
// set color & border
textfield?.borderStyle = .none
textfield?.backgroundColor = UIColor.white
// set corner
textfield?.layer.cornerRadius = (textfield?.frame.size.height ?? 0.0) / 2
//set shadow
textfield?.layer.shadowOpacity = 0.4
textfield?.layer.shadowRadius = 15
textfield?.layer.shadowOffset = CGSize(width: 5, height: 10)
textfield?.layer.shadowColor = UIColor.gray.cgColor
// set icon & Placeholder position
let UIViewController = UIView(frame: CGRect(x: 0, y: 0, width: 60, height: textfield?.frame.size.height ?? 0.0))
let icon = UIImageView(image: UIImage(named: imagename ?? ""))
icon.frame = CGRect(x: 0, y: 0, width: UIViewController.frame.size.width / 4, height: UIViewController.frame.size.height / 4)
icon.center = UIViewController.center
UIViewController.addSubview(icon)
textfield?.leftView = UIViewController
textfield?.leftViewMode = .always
}
}
The question is a little unclear but if you want the keyboard to go down when you've pressed the done button, you should implement the UITextFieldDelegate on your view controller and during the textFieldShouldReturn(_:) method call resignFirstResponder from there:
class myVC: UIViewController, UITextFieldDelegate{
func textFieldShouldReturn(_ textField: UITextField) -> Bool{
return true
}
}
and don't forget to set the delegate in the viewDidLoad for your VC:
override func viewDidLoad() {
super.viewDidLoad()
/// Text field delegate handling
textFieldFirstName.delegate = self
}
https://developer.apple.com/documentation/uikit/uitextfielddelegate/1619603-textfieldshouldreturn?language=objc
By calling resignFirstResponder in viewDidLayoutSubviews, you are resigning the responder every time the viewDidLayoutSubviews is called, i.e. When the bounds change for a view controller's view, the view adjusts the positions of its subviews and then the system calls this method. So I presume the keyboard will be changing the bounds, causing viewDidLayoutSubviews to fire after each keypress and creating the behaviour you are seeing

Adding Progress bar - Swift

I want to add a Progress bar to a tableViewController.
I have one function called HelpersFunctions which do all the calculation.
The function doCalculation is responsible for the calculation.
So, I add the following notification to doCalculation as follow:
NotificationCenter.default.post(name: .return_progress, object: self)
for i in 1...n1 {
//Do all the calculation
}
So, once I reach NotificationCenter.default.post, it will move to a Tableview Controller called CreateNewElementVC
now, inside the ViewDidLoad, I added the following line:
//progress
NotificationCenter.default.addObserver(self, selector: #selector(showProgress), name: .return_progress, object: nil)
In the same swift file, I added the following:
let container_elementProperty: ProgressBarView = {
let view = ProgressBarView()
view.backgroundColor = UIColor(white: 0, alpha: 0.5)
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
#objc func showProgress() {
if(progressCounter > 1.0){timer.invalidate()}
print("Step 1")
container_elementProperty.frame = CGRect(x: 100, y: 100, width: 200, height: 200)
container_elementProperty.backgroundColor = UIColor(white: 0, alpha: 0.5)
container_elementProperty.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleDismiss)))
let queue = DispatchQueue(label: "queue1", qos: .userInteractive)
queue.async {
print("Step 2")
self.view.addSubview(self.container_elementProperty)
}
//view.addSubview(container_elementProperty)
print("Step 3")
container_elementProperty.progress = progressCounter
progressCounter = progressCounter + progressIncrement
let x1: Float = Float(start_Counting)
let x2: Float = Float(End_Counting)
let xx: Float = x1 / x2 * 100
print("Start at: \(xx) %)")
}
So, first I put all the required data in the CreateNewElementVC, then there is a button called run to do all the calculation and then it will move to another TableViewController with all the result.
So while I am inside the function doCalculation, the progress bar should appear .
In fact, the Progress bar container_elementProperty (UIview) appeared just after the calculation is completed which make the progress bar is useless.
Any idea how to make the View called container_elementProperty UIView to be seen ?
I am close to solve this issue as I can see the progress in the stack as below image, I just want to show this on the screen before completing the calculation.
Why I am not able to put the view on the screen while doing the calculation as you can see that step 2 ran first.
The warning related to this issue is: UIView.addSubview(_:) must be used from main thread only.
A Sample Project can be checked on this link at github.
Appreciate any kind of support.
To simulate a progress, such as a network request you cannot simply do a for loop in the main thread, you should use GCD.
To update the progress of an ongoing action use Delegation, not KVO.
Using global variables makes your code flawed, avoid it!
This is a working version of your code.
# Write By Tushar on 11/02/2021#
import UIKit
class ViewController: UIViewController {
let totalValue:Float = 80.0
var firstNumber:Int? = 0
#IBOutlet weak var progressBar: UIProgressView!
override func viewDidLoad() {
super.viewDidLoad()
progressBar.trackTintColor = .white
progressBar.tintColor = .red
// Do any additional setup after loading the view.
progressBar.setProgress(0.0, animated: false)
}
func setProgress(firstValue:Float){
let processValue = firstValue/totalValue
progressBar.setProgress(processValue, animated: true)
}
#IBAction func btnResetClick(_ sender: UIButton) {
print("Reset Button Clicked")
firstNumber = 0
progressBar.setProgress(0.0, animated: false)
}
#IBAction func btnShowProgressClick(_ sender: UIButton) {
guard let presentValue = firstNumber else { return }
let newValue = presentValue + 1
firstNumber = newValue
setProgress(firstValue: Float(firstNumber!))
}
}

Detect if user is moving finger left or right (Swift)

This is not Sprite Kit.
If I have a variable like the one below
var value = 0
How am I able to increase the value if the user drags right and decrease if they drag left?
Thanks!
Like Caleb commented, Ray's tutorial is great, but if you want the actual swift example, please check the next example:
class ViewController: UIViewController, UIGestureRecognizerDelegate {
private var value: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.blackColor()
let recognizer = UIPanGestureRecognizer(target: self, action: Selector("handleDragging:"))
let inputView = UIView(frame: CGRectMake(0, 0, 100, 100))
inputView.backgroundColor = UIColor.whiteColor()
inputView.userInteractionEnabled = true
inputView.addGestureRecognizer(recognizer)
self.view.addSubview(inputView)
}
func handleDragging(recognizer: UIPanGestureRecognizer) {
if (recognizer.state == .Changed) {
let point = recognizer.velocityInView(recognizer.view?.superview)
if (point.x > 0) {
self.value++;
} else {
self.value--;
}
println(self.value)
}
}
}
You can use the velocityInView method of UIPanGestureRecognizer to determine which direction you're going. It returns a CGPoint, so you can pull out the x and y values as you wish. Positive is right/down, negative is left/up.