I have 26 textfields which will provide different float values each, and I want to create a loop to make it easier & faster to add those values into a single variable.
My textfields are named a1, a2, ... a26 so the only thing that changes is the number after 'a'. How can I make swift to recognize the textfield name as I'm increasing the variable 'x'.
I was thinking something like this:
var x : Int = 1
var total : Float = 0;
var y : Float = 0;
while (x == 26) {
y = a/(x).floatValue;
total += y;
x+=1;
}
My idea is obviously not working 😕
Make an array containing all 26 text fields, and iterate over them:
let textfields = [a1, a2, ..., a26]
for textfield in textfields {
total += textfield.floatValue
}
I agree with #jtbandes' comment that it is best to start with arrays and loops, so you never need all 26 to be separate variables. If your textFields are #IBOutlets, you can use an outlet collection (which is just a single array to hold all of the textFields).
#IBOutlet var textfields: [NSTextField]!
Then you'd simply do:
for textfield in textfields {
total += textfield.floatValue
}
In the interest of showing what is possible, assuming the textfields are properties of your class (and your class derives from NSObject), you can use key-value coding to get the values:
for i in 1...26 {
if let tf = value(forKey: "a\(i)") as? NSTextField {
total += tf.floatValue
}
}
Full example for macOS:
import Cocoa
class ViewController: NSViewController {
var a1 = NSTextField()
var a2 = NSTextField()
var a3 = NSTextField()
func total() {
// give them some values for testing purposes
a1.stringValue = "1.2"
a2.stringValue = "2.3"
a3.stringValue = "3.14"
var total: Float = 0.0
for i in 1...3 {
if let tf = value(forKey: "a\(i)") as? NSTextField {
total += tf.floatValue
}
}
print(total)
}
}
ViewController().total() // 6.64
Full example for iOS:
import UIKit
class ViewController: UIViewController {
var a1 = UITextField()
var a2 = UITextField()
var a3 = UITextField()
func total() {
// give them some values for testing purposes
a1.text = "1.2"
a2.text = "2.3"
a3.text = "3.14"
var total: Float = 0.0
for i in 1...3 {
if let tf = value(forKey: "a\(i)") as? UITextField {
total += ((tf.text ?? "") as NSString).floatValue
}
}
print(total)
}
}
ViewController().total() // 6.64
Related
I'm a newbie in swift and I'm trying to pass an instance of a class (CP) I made as a property of the ContentView struct for swift UI. This instance takes in parameter another property of the same ContentView struct. I want to be able to select an option from the alert menu, set the property to a variable that will determinate specific time sequences from a chronometer, and since the parameter of this instance is an inout string, the values will update in the instance. I found somebody in a similar situation on this website, and somebody suggested overriding the viewDidLoad function. However this does not apply to the SwiftUI framework.
Here is a sample of the code I believe is enough to understand the issue. débatCP, will be used in other elements so declaring it inside the first button action will not work.
struct ContentView: View {
#State var a = "Chronometre"
#State var partie = "Partie du débat"
#State var tempsString = "Temps ici"
#State var enCours = "CanadienParlementaire - Commencer"
#State var pausePlay = "pause"
#State var tempsMillieu = 420;
#State var tempsFermeture = 180
#State var round = 7;
#State var répartitionPM = "7/3"
var debatCP = CP(modePM: &répartitionPM)
#State private var showingAlert = false
//il va falloir un bouton pause
var body: some View {
VStack {
Text(String(self.round))
Text(a)
.font(.largeTitle)
.fontWeight(.semibold)
.lineLimit(nil)
Text(partie)
Button(action: {
self.showingAlert = true
if self.enCours != "Recommencer"{
let chrono = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { (chrono) in
self.debatCP.verifierEtatDebut(round: &self.round, tempsActuel: &self.tempsMillieu, pause: &self.pausePlay, partie: &self.partie, tempsStr: &self.tempsString)
if self.round <= 2{
self.debatCP.verifierEtatFin(round: &self.round, tempsActuel: &self.tempsFermeture, pause: &self.pausePlay, partie: &self.partie, tempsStr: &self.tempsString)
}
if self.round <= 0 {
chrono.invalidate()
self.enCours = "Canadien Parlementaire - Commencer"
}
}
}
else{
self.tempsMillieu = 420
self.tempsFermeture = 180
self.round = 7
}
self.enCours = "Recommencer"
self.a = "CP"
}, label: {
Text(enCours)
.alert(isPresented: $showingAlert) {
Alert(title: Text("6/4 ou 7/3"), message: Text("6/4 pour avoir plus de temps à la fin et 7/3 pour en avoir plus au début"), primaryButton: .default(Text("6/4"), action: {
self.répartitionPM = "6/4";
}), secondaryButton: .default(Text("7/3"), action: {
self.répartitionPM = "7/3"
}))
}
})
I get this error message : Cannot use instance member 'répartitionPM' within property initializer; property initializers run before 'self' is available
Edit
Here's the CP class
class CP:Debat{
init(modePM: inout String){
}
override var rondeFermeture:Int{
return 2;
}
override var tempsTotalMillieu : Int{
return 420;
};
override var tempsProtege:Int{//60
return 60;
}
override var tempsLibre:Int{//360
return 360;
}
override var tempsFermeture: Int{
return 180;
}
Along with a sample of the Defat Class
class Debat{
var rondeFermeture:Int{
return 0;
}
var tempsTotalMillieu:Int{//420
return 0;
};
var tempsProtege:Int{//60
return 0;
}
var tempsLibre:Int{//360
return 0;
}
var tempsFermeture:Int{
return 0
}
func formatTime(time:Int) -> String {
let minutes:Int = time/60
let seconds:Int = time%60
return String(minutes) + ":" + String(seconds)
}
func prochainTour(time: inout Int, round : inout Int)->Void{
if round > rondeFermeture {
let tempsAvantLaFin = time % self.tempsTotalMillieu
time -= tempsAvantLaFin
}
if round <= rondeFermeture{
let tempsAvantLaFin = time % self.tempsFermeture
time -= tempsAvantLaFin
}
}
#State var répartitionPM = "7/3"
var debatCP = CP(modePM: &répartitionPM)
The SwiftUI has different pattern to work with #State variables. It is not clear what is CP but anyway the following is the way to go
struct AContentView: View {
#State var repartitionPM = "7/3" // should be defined
var debatCP:CP!=nil // declare-only
init() {
debatCP = CP(modePM: $repartitionPM) // pass as binding
}
...
and CP should be something like
class CP:Debat{
#Binding var modePM:String
init(modePM:Binding <String>){
self._modePM = modePM
super.init()
}
for a very long time I can’t solve a simple problem, namely: transferring a word to a new line and automatically reducing the label if the word does not fit. Tell me how to be in this situation.
#IBOutlet weak var wordLabel: UILabel!
#IBOutlet weak var transcriptionLabel: UILabel!
#IBOutlet weak var translationLabel: UILabel!
var index = 0
var word = ""
var transcription = ""
var translation = ""
override func viewDidLoad() {
super.viewDidLoad()
wordLabel.text = word
transcriptionLabel.text = ""
translationLabel.text = ""
wordLabel.adjustsFontSizeToFitWidth = true
wordLabel.minimumScaleFactor = 0.1
transcriptionLabel.adjustsFontSizeToFitWidth = true
transcriptionLabel.minimumScaleFactor = 0.1
translationLabel.adjustsFontSizeToFitWidth = true
translationLabel.minimumScaleFactor = 0.1
self.transcriptionLabel.alpha = 0.0
self.translationLabel.alpha = 0.0
}
Thanks humblePilgrim, for the suggested answer, it really helped. I leave the link to the source and attach the answer itself.
source
Swift 4.2
extension UILabel {
// Adjusts the font size to avoid long word to be wrapped
func fitToAvoidWordWrapping() {
guard adjustsFontSizeToFitWidth else {
return // Adjust font only if width fit is needed
}
guard let words = text?.components(separatedBy: " ") else {
return // Get array of words separate by spaces
}
// I will need to find the largest word and its width in points
var largestWord: NSString = ""
var largestWordWidth: CGFloat = 0
// Iterate over the words to find the largest one
for word in words {
// Get the width of the word given the actual font of the label
let wordWidth = word.size(withAttributes: [.font: font]).width
// check if this word is the largest one
if wordWidth > largestWordWidth {
largestWordWidth = wordWidth
largestWord = word as NSString
}
}
// Now that I have the largest word, reduce the label's font size until it fits
while largestWordWidth > bounds.width && font.pointSize > 1 {
// Reduce font and update largest word's width
font = font.withSize(font.pointSize - 1)
largestWordWidth = largestWord.size(withAttributes: [.font: font]).width
}
}
}
I thank user Dam for the answer.
I'm using Xcode 10.1 where I tried to calculate the radius in a completion block by setting the value of another variable called as diameter. The code is given below
import UIKit
import Foundation
var radius:Double = 10
var diameter: Double{
get{
return radius * 2
}
set{
radius = newValue
}
}
diameter = 30
print(diameter)
I'm expecting that the output should be 60.0 but it showing:
error: Computed property.playground:43:1: error: variables currently
must have an initial value when entered at the top level of the REPL
var area: Double{
The output is properly shown in the online compiler of swift but not in Xcode. Now How to get the proper output?
You have to write this code in a class or struct then create the object of the class or struct then you have to assign diameter value and print it.
/**
This code you can run in playground
*/
class Demo {
var radius:Double = 10
var diameter: Double {
get{
return radius * 2
}
set{
radius = newValue
}
}
}
let d = Demo()
d.diameter = 30
print(d.diameter)
//Output
60.0
/**
This code you can run in Xcode.
*/
class ViewController: UIViewController {
var radius:Double = 10
var diameter: Double {
get{
return radius * 2
}
set{
radius = newValue
}
}
override func viewDidLoad() {
super.viewDidLoad()
diameter = 30
print(diameter) // Output: 60.0
}
}
I'm trying to build up a animation library and I have this object I am referring to as a MedicalCard. It's a subclass of UIView. Nothing particularly fancy about it, there's a couple of properties:
var isRotated: Bool = false
var idx: Int = -1
let btnToggle = UIButton()
In my viewcontroller I create a bunch of these by looping through a dictionary that has some stored values for each card:
let arrCardData = [
["color":UIColor.lightGray, "title":"Card 1"],
["color":UIColor.black, "title":"Card 2"],
["color":UIColor.red, "title":"Card 3"],
["color":UIColor.green, "title":"Card 4"]
]
and then after the instantiation I store them in an array:
var idx = -1
var cardY = 50.0
let cardW = self.view.frame.size.width
for dictThisCard in arrCardData {
idx = idx + 1
let thisCard = MedicalCard()
thisCard.backgroundColor = dictThisCard["color"] as? UIColor
thisCard.frame = CGRect(x:0.0, y:cardY, width:Double(cardW), height:308.0)
thisCard.idx = idx
thisCard.buildCard()
arrCards.append(thisCard)
cardY = cardY + 100.0
self.view.addSubview(thisCard)
}
so far so good, I can add a print(thisCard.idx) and get that value in the log.
In the medical card class I have a button that starts the animation, it simply calls back to the viewcontroller through a notification which card's button was touched:
func toggleCard(_ myButton:UIButton) {
//call back to move cards
let noteName = Notification.Name("animateCards")
NotificationCenter.default.post(name:noteName, object:nil, userInfo:["idx":self.tag])
}
here's where I am falling off the rails, back in the viewcontroller I get the card idx from the notification and then retrieve that card from the array. I can log it and based on the frame and memory address it's the right card:
func animateCards(note:Notification) -> Void {
//get card from idx passed
let dictUserInfo = note.userInfo
let cardIDX = dictUserInfo!["idx"] as! Int
let thisCard = arrCards[cardIDX];
var angle: CGFloat = CGFloat.pi/2
print(thisCard)
print(arrCards[cardIDX])
}
here's the log:
<AnimationPlayground.MedicalCard: 0x7f91bdf0af10; frame = (0 50; 375 308); layer = <CALayer: 0x618000037360>>
<AnimationPlayground.MedicalCard: 0x7f91bdf0af10; frame = (0 50; 375 308); layer = <CALayer: 0x618000037360>>
but I can't see the properties here.
I have a meter laid out on a storyboard that is comprised of ten segments. There are an additional ten segments on top of the original segments that are colored to simulate a lighted segment - these are outlets. (See image below.) Currently I am using a switch statement to hide and unhide each outlet/segment based on a constantly varying input level. But, as you can see in the code below, it's not pretty. I keep reading that polymorphism is often the way to improve a switch statement, but I can't see how that would help here.
switch input {
case 0...9:
seg1.hidden = false
seg2.hidden = true
seg3.hidden = true
seg4.hidden = true
seg5.hidden = true
seg6.hidden = true
seg7.hidden = true
seg8.hidden = true
seg9.hidden = true
seg10.hidden = true
case 10...19:
seg1.hidden = false
seg2.hidden = false
seg3.hidden = true
seg4.hidden = true
seg5.hidden = true
seg6.hidden = true
seg7.hidden = true
seg8.hidden = true
seg9.hidden = true
seg10.hidden = true
...
and on and on for eight more levels.
//You can have these segments in an array like this-
let segments = [seg1, seg2, seg3, seg4, seg5,seg6, seg7, seg8, seg9, seg10]
// Function that will setup the segments based on input
func setUpSegmentsForInputValue(segments:[UIView], value:Int) {
for (index, segment) in segments.enumerate() {
segment.hidden = (value/10) != index
}
}
// Call the function with whatever input values
setUpSegmentsForInputValue(segments: segments, value: someValue)
You could add all of your outlets to an array. Map the input to an "index range" and then decide in a for loop if outlet is in "index range". Then set hidden property accordingly.
#IBOutlet weak var seg1 = UIView()
#IBOutlet weak var seg2 = UIView()
#IBOutlet weak var seg3 = UIView()
#IBOutlet weak var seg4 = UIView()
...
let segs = [seg1, seg2, seg3, seg4]
let input = 19
let range = 0 ... input / 10
for (index, seg) in segs.enumerate() {
if range ~= index {
seg.hidden = false
}else {
seg.hidden = true
}
}
Alternative (instead of the for loop):
#IBOutlet weak var seg1 = UIView()
#IBOutlet weak var seg2 = UIView()
#IBOutlet weak var seg3 = UIView()
#IBOutlet weak var seg4 = UIView()
...
let segs = [seg1, seg2, seg3, seg4]
let input = 19
let range = 0 ... input / 10
segs.enumerate().forEach { index, seg in
seg.hidden = !(range ~= index)
}
Hope this helps.