What I am trying to do is send the value of a variable from a view to a class, but I keep getting an error. Not sure what to do.
Here is the class:
class perfCalcDep: ObservableObject {
var tom:Double
var arm:Double
#Published var tempDep:String = ""
#Published var elevDep:String = ""
#Published var qnhDep:String = ""
#Published var windDep:String = ""
#Published var slopeDep:String = ""
#Published var rwyCondDep = 0
var altDep: Double {
let pressCalc = (1013 - (Double(qnhDep) ?? 1013)) * 30
return (Double(elevDep) ?? 0) + pressCalc
}
var altVar : Double { 0.21 * altDep }
var tempVar : Double { 24 * (Double(tempDep) ?? 0) }
var windVar : Double { 20.67 * (Double(windDep) ?? 0) }
var tomVar : Double { 2.22 * Double(2550-Double(tom)) }
var slpVar : Double { (Double(slopeDep) ?? 0) / 2 }
var tod : Double { (1700 + altVar + tempVar - tomVar - windVar) }
var todr : Double {
if rwyCondDep == 1 {
return (tod + ((0.1 * tod) * slpVar)) * 1.2
} else if rwyCondDep == 2 {
return (tod + ((0.1 * tod) * slpVar)) * 1.3
} else {
return (tod + ((0.1 * tod) * slpVar))
}
}
init(tom:Double, arm:Double) {
self.tom = tom
self.arm = arm
}
}
And here is part of the view:
struct TakeOffPerf: View {
// The variables I want to send to the class - their values are received from the previous view.
var tMss:Double
var tArm:Double
#ObservedObject var performance = perfCalcDep(tom: tMss, arm: tArm) // Error: Cannot use instance member 'tArm' within property initializer; property initializers run before 'self' is available
#ObservedObject var settings = Settings()
var body: some View {,,,} // just a list that shows the values from the class
Any help would be greatly appreciated.
Properties are initialised before self, so you cannot make initializing dependency between properties, but you can do this in init, eg.
struct TakeOffPerf: View {
var tMss:Double
var tArm:Double
#ObservedObject var performance: perfCalcDep // << only declare !!
#ObservedObject var settings = Settings()
init(tMss:Double, tArm:Double) {
self.tArm = tArm
self.tMss = tMss
self.performance = perfCalcDep(tom: tMss, arm: tArm)
}
// ... other code
}
Note: preserved original style, but it is good practice to name types capitalised, like PerfCalcDep
I'm curious about the default implementation of AnyView in SwiftUI. How to put structs with different generic types into a protocol array?
For example:
let a = AnyView(Text("hello"))
let b = AnyView(Image(systemName: "1.circle"))
let genericViews = [a, b] // No compile error
And my implementation:
struct TypeErasedView<V: View>: View {
private var _view: V
init(_ view: V) {
_view = view
}
var body: V {
_view
}
}
let a = TypeErasedView(Text("Hello"))
let b = TypeErasedView(Image(systemName: "1.circle"))
let genericViews = [a, b] // compile error
The compile error will be "Heterogeneous collection literal could only be inferred to '[Any]'; add explicit type annotation if this is intentional".
Does anyone have any ideas?
Here is a demo of possible approach. It is simplified, but shows the generic idea of how this might be done... or at least a direction.
Full compilable & working module. Tested on Xcode 11.2 / iOS 13.2
import SwiftUI
private protocol TypeErasing {
var view: Any { get }
}
private struct TypeEraser<V: View>: TypeErasing {
let orinal: V
var view: Any {
return self.orinal
}
}
public struct MyAnyView : View {
public var body: Never {
get {
fatalError("Unsupported - don't call this")
}
}
private var eraser: TypeErasing
public init<V>(_ view: V) where V : View {
eraser = TypeEraser(orinal: view)
}
fileprivate var wrappedView: Any { // << they might have here something specific
eraser.view
}
public typealias Body = Never
}
struct DemoAnyView: View {
let container: [MyAnyView]
init() {
let a = MyAnyView(Text("Hello"))
let b = MyAnyView(Image(systemName: "1.circle"))
container = [a, b]
}
var body: some View {
VStack {
// dynamically restoring types is different question and might be
// dependent on Apple's internal implementation, but here is
// just a demo that it works
container[0].wrappedView as! Text
container[1].wrappedView as! Image
}
}
}
struct DemoAnyView_Previews: PreviewProvider {
static var previews: some View {
DemoAnyView()
}
}
It's because there's a generic constraint on yours. AnyView has no generic constraint. You instantiate it with an underlying generic View, but its Body is always declared as Never. There might be compiler magic happening here as I couldn't get a generic constraint-less version to work.
I have a struct like this
struct Str{
let item1: UINT16
let item2: UINT16
let item3: UINT32
}
I got a struct var mystr
var mystr = Str(item1: 0x0101, item2: 0xffff, item3: 4)
And I've store the struct var into a Data var
var myData = Data(bytes: &mystr, count:MemoryLayout<Str>.size)
My question is how to use the Data var to initialize a new Str var(store the value of the Data into the struct)
Thanks
I'm not enough of an expert to tell how dangerous just dumping some memory to a Data object and then using that memory to init a struct is, but here is what you could do:
let strFromData = myData.withUnsafeBytes { (p: UnsafePointer<Str>) -> Str in
return p.pointee
}
Or even shorter:
let strFromData2 = myData.withUnsafeBytes { $0.pointee as Str }
What I have done in former projects is to process a struct's members one by one:
extension Data
{
mutating func append<T>(value: T)
{
var v = value
self.append(UnsafeBufferPointer(start: &v, count: 1))
}
}
var data = Data()
data.append(value: mystr.item1)
data.append(value: mystr.item2)
data.append(value: mystr.item3)
Building a Str from myData using Str's initializer:
let strFromData3 = myData.withUnsafeBytes { (p: UnsafePointer<UInt16>) -> Str in
let item1 = p[0]
let item2 = p[1]
let p2 = UnsafeRawPointer(p).bindMemory(to: UInt32.self, capacity: 2)
let item3 = p2[1]
return Str(item1: item1, item2: item2, item3: item3)
}
Here is the main screen of the app :
I have successfully linked these pickerView to each other, then I have tried to assign the calculation in converting but not to avail :
ex: mile to killo
but I could not find a way to do so. I have tried to use "Switch" still nothing happen. I just need someone can show me how can I convert from a certain unit to another unit through the textFields. For example, if you enter a value in a certain texField the converted result will in the other textField and vise versa.
Here is my Code:
import UIKit
class ViewController: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate, UITextFieldDelegate{
#IBOutlet weak var mainPicker: UIPickerView!
#IBOutlet weak var leftPicker: UIPickerView!
#IBOutlet weak var rightPicker: UIPickerView!
#IBOutlet weak var textFieldLeft: UITextField!
#IBOutlet weak var textFielfRight: UITextField!
#IBOutlet weak var equal: UILabel!
var leftPickerData : [String] = []
var rightPickerData : [String] = []
var dataDict:NSMutableDictionary!
var mainPickerData:NSArray!
var leftRightPickerData:NSArray!
//yourPicker.backgroundColor = UIColor(patternImage: UIImage(named: "back.jpg")!)
override func viewDidLoad() {
super.viewDidLoad()
mainPicker.backgroundColor = .clear
rightPicker.backgroundColor = .clear
leftPicker.backgroundColor = .clear
// Connect data to ViewController ..
self.mainPicker.delegate = self
self.mainPicker.dataSource = self
self.leftPicker.delegate = self
self.leftPicker.dataSource = self
self.rightPicker.delegate = self
self.rightPicker.dataSource = self
self.textFieldLeft.delegate = self
self.textFielfRight.delegate = self
let theWidth = view.frame.size.width
let theHeight = view.frame.size.height
mainPicker.center = CGPoint(x: theWidth/2, y: theHeight/2 - 182.5)
leftPicker.center = CGPoint(x: theWidth/2 - 100, y: theHeight/2)
rightPicker.center = CGPoint(x: theWidth/2 + 100, y: theHeight/2)
textFieldLeft.center = CGPoint(x: theWidth/2 - 90, y: theHeight/2 + 110)
textFielfRight.center = CGPoint(x: theWidth/2 + 90, y: theHeight/2 + 110)
equal.center = CGPoint(x: theWidth/2, y: theHeight/2 + 110)
dataDict = ["Area":["Square Mile", "Square Yard", "Square Foot", "Square Inch", "Hectare", "Acre", "Square Kilometer", "Square Meter", "Square Centimeter", " Square Millimeter"]
,"Energy":["Btus", "Calories", "Ergs", "Foot-Pounds", "Joules", "Kilogram-Calories", "Kilogram-Meters", "Kilowatt-Hours", "Newton-Meters", "Watt-Hours"], "Length":["Mile", "Yard", "Foot", "Inch", "Kilometer", "Meter", "Centimeter", "Millimeter"], "Power": ["Btus/Minute", "Foot-Pounds/Min", "Foot-Pounds/Sec", "Horsepower", "Kilowatts", "Watts"], "Pressure": ["Pounds/Sqr Ft", "Pounds/Sqr In", "Atmospheres", "Bars", "In of Mercury", "Cm of Mercury", "Kilograms/Sqr Meter", "Pascals"], "Speed": ["Knots", "Miles/Hr", "Miles/Min", "Feet/Min", "Feet/Sec", "Kilometers/Hr", "Kilometer/Min", "Meters/Sec"], "Temperature": ["Celsius C˚", "Fahrenheit", "Kelvin"], "Time": ["Years", "Months", "Weeks", "Days", "Hours", "Minutes", "Seconds", "Millisconds", "Microseconds", " Nanoseconds"], "Volume": ["Cupic Feet","Cubic Meter", "Gallon (Imp)", "Gallon (US)", "Quart (US)", "Pint (US)", "Fluid Oz", "Cup", "Tablespoon", "Teaspoon", "Dram (US)", "Liter"], "Weight": ["Short Ton (US)","Long Ton (UK)", "Pound (U.S)", "Ounce (US)", "Stone", "Metric Ton", "Kilogram", "Gram"]]
mainPickerData = dataDict.allKeys as NSArray!;
leftRightPickerData = dataDict.object(forKey: mainPickerData.firstObject as! String) as! NSArray
// Linking the textFields with the pickerViews.
//textFieldLeft.inputView = leftPicker;
// textFielfRight.inputView = rightPicker;
}
// The number of columns of data
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
// The number of rows of data
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
switch (pickerView.tag) {
case mainPicker.tag:
return mainPickerData.count
case leftPicker.tag,rightPicker.tag:
let currentSelectedIndex = mainPicker.selectedRow(inComponent: component)
leftRightPickerData = (dataDict.object(forKey: mainPickerData[currentSelectedIndex] as! String) as! NSArray)
return leftRightPickerData.count;
default:
break;
}
return 0;
}
// The data to return for the row and component (column) that's being passed in
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
if leftPicker.tag == 2 {
return leftPickerData[row]
}else if rightPicker.tag == 3{
return rightPickerData[row]
}
return ""
}
// Catpure the picker view selection
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
// This method is triggered whenever the user makes a change to the picker selection.
// The parameter named row and component represents what was selected.
if(pickerView.tag == 1 ){
let currentSelectedIndex = mainPicker.selectedRow(inComponent: component)
leftRightPickerData = (dataDict.object(forKey: mainPickerData[currentSelectedIndex] as! String) as! NSArray)
leftPicker.reloadAllComponents()
rightPicker.reloadAllComponents()
if mainPicker.tag == mainPicker.selectedRow(inComponent: component) {
if leftPicker.tag == leftPicker.selectedRow(inComponent: component) && rightPicker.tag == rightPicker.selectedRow(inComponent: component){
textFieldLeft.text = textFielfRight.text
}
}
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textFieldLeft.resignFirstResponder()
textFielfRight.resignFirstResponder()
return true
}
func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? {
let titleData : String?
if(pickerView.tag == mainPicker.tag){
titleData = mainPickerData[row] as? String;
}
else{
let currentSelectedIndex = mainPicker.selectedRow(inComponent: 0)
leftRightPickerData = (dataDict.object(forKey: mainPickerData[currentSelectedIndex] as! String) as! NSArray)
titleData = leftRightPickerData[row] as? String;
}
let myTitle = NSAttributedString(string: titleData!, attributes: [NSFontAttributeName:UIFont(name: "Georgia", size: 12.0)!,NSForegroundColorAttributeName:UIColor.blue])
return myTitle;
}
}
I would do something like this as an example for different length values:
You would want to store some sort of array that tracks conversion values for each different unit of measurement, the best universal length unit is meters. It would be a good idea to make sure the corresponding value for each unit stays static, so create an enum:
enum Length: Int {
case mile = 0
case yard = 1
case foot = 2
case inch = 3
case kilometer = 4
// ... keep going with all lenght units of measurement
}
Then you should make your array of values for conversions, this is an array of double's based on the length values I listed in the enum above for conversion to meters:
// Store values with corresponding indicies in array to the Length enum's values
let meterConversions:[Double] = [1609.34, 0.9144, 0.3048, 0.0254, 1,000]
// Store values of length unit descriptions to print after conversion in TextField, long hand or short hand whatever you prefer. (this is optional)
let lengthUnits = ["Mile", "Yard", "Foot", "Inch", "Kilometer"]
Then create some conversion methods:
// Convert length type to meters
func convertToMeters(type: Length, unitValue: Double) -> Double {
return (meterConversions[type.rawValue] * unitValue)
}
// Convert meters back to length type
func convertFromMeters(type: Length, meterValue: Double) -> Double {
return meterValue/meterConversions[type.rawValue]
}
// Convert from length type to other length type
func convertType(from: Length, to: Length, unitValue: Double) -> Double {
// Convert from value to meters to start
let fromValueToMeters:Double = convertToMeters(type: from, unitValue: unitValue)
// Now use that value to convert back to desired unit
let newUnitValue:Double = convertFromMeters(type: to, meterValue: fromValueToMeters)
return newUnitValue
}
Then whenever the user selects a new row in either the left or right UIPickerView, update the calculation and handle it however you wish:
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
// Make sure there is actually some text in the left text field, otherwise return
guard let text = textFieldLeft.text else { print("textFieldLeft doesn't contain any text"); return }
// You are going to need a numerical (Double) value from the user's text
let stringAsDouble = Double(text)
// Now check that the text was actually a numerical value able to be converted to a double
if let value = stringAsDouble {
var typeA:Length!
var typeB:Length!
if pickerView == leftPicker {
typeA = Length(rawValue: row)!
typeB = Length(rawValue: rightPicker.selectedRow(inComponent: 0))!
converted = convertType(from: typeA, to: typeB, unitValue: value)
}
else if pickerView == rightPicker {
typeA = Length(rawValue: leftPicker.selectedRow(inComponent: 0))!
typeB = Length(rawValue: row)!
let value:Double = 0 // Determine user entered value from textField
converted = convertType(from: typeA, to: typeB, unitValue: value)
}
updateValueAfterConversion(originalValue: value, originalType: typeA, convertedValue: converted, convertedType: typeB)
} else {
print("Couldn't convert text to double value")
}
}
func updateValueAfterConversion(originalValue: Double, originalType: Length, convertedValue: Double, convertedType: Length) {
// Update text in both fields, lengthUnits part is optional if you want to print unit along with value.
// Update text on left side
textFieldLeft.text = "\(originalValue) \(lengthUnits[originalType.rawValue])"
// Update text on right side
textFieldRight.text = "\(convertedValue) \(lengthUnits[convertedType.rawValue])"
}
I will give you some tips:
Value in a text field is a String, you should convert it to Double and then multiply by different value between 2 units.
To save these different values, I have an idea: save [1, 1760, 5280, 63360, 1.609344, 1609.344, 160934.4, 1609344] for ["Mile", "Yard", "Foot", "Inch", "Kilometer", "Meter", "Centimeter", "Millimeter"] then do some math caculating.
You should listen to the notification UITextFieldTextDidChange for each UITextField to instantly calculate the equal value.
If you are a lazy developer, I found this for you: https://github.com/michalkonturek/MKUnits
Hope this helps.
After some heavy searching, I finally found a very intuitive way of initializing a 3D array in Swift:
var firstArray = [Int](count:4, repeatedValue: 0)
var secondArray = [[Int]](count:4, repeatedValue: firstArray)
var thirdArray = [[[Int]]](count:4, repeatedValue: secondArray)
It works great. I can access any value of the thirdArray:
thirdArray[a][b][c]
, just like in C++.
But what if I have a struct like:
struct myStruct
{
var color: UIColor = UIColor.redColor()
var number: Int = 0
var used: Bool = true
}
How do I use now repeatedValue?
var firstArray = [myStruct](count:4, repeatedValue: ???)
Simply use:
var newArray = [myStruct](count:4, repeatedValue: myStruct())
The syntax for creating instances of structs and classes is the same.