I have a composable with with a remembered value called imageWidth
var imageWidth = remember { 0f }
I want to calculated the image width once (unless rotation has changed). This is the only place that writes to that variable.
LaunchedEffect(key1 = currentRotation) {
val ratio = imageBitmap.getRatio(currentRotation)
imageWidth = //some calculation
}
The imageWidth is accessed by multiple places in the app. Using some log prints, I have made sure that this side effect is being called and correct value is set to imageWidth for the first time I enter the screen.
For some reason, by clicking on some button, the value is being reset to 0.
What are the cases that a remembered value can be forgotten/reset?
If the reason is the disposal of the composable, why isn't the LaunchedEffect called again and calculate the value when entering the composition?
With your current set up what supposed to happen is imageWidth to be set to 0f after each recomposition because remember is run on composition or any of its key are changed.
/**
* Remember the value returned by [calculation] if all values of [keys] are equal to the previous
* composition, otherwise produce and remember a new value by calling [calculation].
*/
#Composable
inline fun <T> remember(
vararg keys: Any?,
calculation: #DisallowComposableCalls () -> T
): T {
var invalid = false
for (key in keys) invalid = invalid or currentComposer.changed(key)
return currentComposer.cache(invalid, calculation)
}
In LaunchedEffect you set value but when another recomposition happens it's reset to value in remember block.
However something i might have missed here is by rotation if you mean rotating device and recreating Activity, the answer below doesn't work, you can move your value to ViewModel to store latest value or rememberSavable.
If your Activity is not recreated but you want to recalculate block inside remember add keys to check if there has to be new calculation.
You need to add currentRotation as key for it to be set only when rotation changes
var imageWidth = remember(currentRotation) {
val ratio = imageBitmap.getRatio(currentRotation)
//some calculation result as float
}
remember with keys is commonly used in default Composable source codes, but i wonder why it' not mentioned in official documents.
Slider for instance use it as
#Composable
fun Slider(
value: Float,
onValueChange: (Float) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
valueRange: ClosedFloatingPointRange<Float> = 0f..1f,
/*#IntRange(from = 0)*/
steps: Int = 0,
onValueChangeFinished: (() -> Unit)? = null,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
colors: SliderColors = SliderDefaults.colors()
) {
require(steps >= 0) { "steps should be >= 0" }
val onValueChangeState = rememberUpdatedState(onValueChange)
val tickFractions = remember(steps) {
stepsToTickFractions(steps)
}
// rest of the code
}
And in painterResource code
#Composable
#ComposableOpenTarget(-1)
fun rememberVectorPainter(
defaultWidth: Dp,
defaultHeight: Dp,
viewportWidth: Float = Float.NaN,
viewportHeight: Float = Float.NaN,
name: String = RootGroupName,
tintColor: Color = Color.Unspecified,
tintBlendMode: BlendMode = BlendMode.SrcIn,
autoMirror: Boolean = false,
content: #Composable #VectorComposable (viewportWidth: Float, viewportHeight: Float) -> Unit
): VectorPainter {
val density = LocalDensity.current
val widthPx = with(density) { defaultWidth.toPx() }
val heightPx = with(density) { defaultHeight.toPx() }
val vpWidth = if (viewportWidth.isNaN()) widthPx else viewportWidth
val vpHeight = if (viewportHeight.isNaN()) heightPx else viewportHeight
val intrinsicColorFilter = remember(tintColor, tintBlendMode) {
if (tintColor != Color.Unspecified) {
ColorFilter.tint(tintColor, tintBlendMode)
} else {
null
}
}
return remember { VectorPainter() }.apply {
// These assignments are thread safe as parameters are backed by a mutableState object
size = Size(widthPx, heightPx)
this.autoMirror = autoMirror
this.intrinsicColorFilter = intrinsicColorFilter
RenderVector(name, vpWidth, vpHeight, content)
}
}
This is a very common approach in many default Composables and the most common one is LaunchedEffect
#Composable
#NonRestartableComposable
#OptIn(InternalComposeApi::class)
fun LaunchedEffect(
key1: Any?,
block: suspend CoroutineScope.() -> Unit
) {
val applyContext = currentComposer.applyCoroutineContext
remember(key1) { LaunchedEffectImpl(applyContext, block) }
}
which runs code block on first composition or when at least one of they keys change.
Related
I'm trying to understand how to change a struct (or class?) in an array by using reduce. Creating 4 countdown timers, on tap pause the current timer and start the next. So I tried something like this:
var timer1 = CountdownTimer()
// var timer2 = CountdownTimer() etc.
.onTapGesture(perform: {
var timers: [CountdownTimer] = [timer1, timer2, timer3, timer4]
var last = timers.reduce (false) {
(setActive: Bool, nextValue: CountdownTimer) -> Bool in
if (nextValue.isActive) {
nextValue.isActive = false;
return true
} else {
return false
}
}
if (last) {
var timer = timers[0]
timer.isActive = true
}
})
############# CountdownTimer is a struct ######
struct CountdownTimer {
var timeRemaining: CGFloat = 1000
var isActive: Bool = false
}
This does not work, two errors I'm seeing
the timers in the array are copies of the timers, not the actual timer so changing them doesn't actually change the timers being displayed on screen.
nextValue (i.e. the next timer) can't be changed because it's a let variable in the reduce declaration. I don't know how to change this (or if it's even relevant because presumably it's a copy of the copy of the timer and not the one I actually want to change).
Am I approaching this in a way thats idiomatically wrong for Swift? How should I be changing the original timers?
I agree with Paul about the fact that this should likely all be pulled out into an observable model object. I'd make that model hold an arbitrary list of timers, and the index of the currently active timer. (I'll give an example of that at the end.)
But it's still worth exploring how you would make this work.
First, SwiftUI Views are not the actual view on the screen like in UIKit. They are descriptions of the view on the screen. They're data. They can be copied and destroyed at any time. So they're readonly objects. The way you keep track of their writable state is through #State properties (or similar things like #Binding, #StateObject, #ObservedObject and the like). So your properties need to be marked #State.
#State var timer1 = CountdownTimer()
#State var timer2 = CountdownTimer()
#State var timer3 = CountdownTimer()
#State var timer4 = CountdownTimer()
As you've discovered, this kind of code doesn't work:
var timer = timer1
timer.isActive = true
That makes a copy of timer1 and modifies the copy. Instead, you want WriteableKeyPath to access the property itself. For example:
let timer = \Self.timer1 // Note capital Self
self[keyPath: timer].isActive = true
Finally, reduce is the wrong tool for this. The point of reduce is to reduce a sequence to a single value. It should never have side-effects like modifying the values. Instead, you just want to find the right elements, and then change them.
To do that, it would be nice to be able to easily track "this element and the next one, and the last element is followed by the first." That seems very complicated, but it's surprisingly simple if you include Swift Algorithms. That gives cycled(), which returns a Sequence that repeats its input forever. Why is that useful? Because then you can do this:
zip(timers, timers.cycled().dropFirst())
This returns
(value1, value2)
(value2, value3)
(value3, value4)
(value4, value1)
Perfect. With that I can fetch the first active timer (keypath) and its successor, and update them:
let timers = [\Self.timer1, \.timer2, \.timer3, \.timer4]
if let (current, next) = zip(timers, timers.cycled().dropFirst())
.first(where: { self[keyPath: $0.0].isActive })
{
self[keyPath: current].isActive = false
self[keyPath: next].isActive = true
}
That said, I wouldn't do that. There are subtle requirements here that should be captured in a type. In particular, you have this assumption that there is only one active timer, but nothing enforces that. If that's what you mean, you should make a type that says so. For example:
class TimerBank: ObservableObject {
#Published private(set) var timers: [CGFloat] = []
#Published private(set) var active: Int?
var count: Int { timers.count }
init(timers: [CGFloat]) {
self.timers = timers
self.active = timers.startIndex
}
func addTimer(timeRemaining: CGFloat = 1000) {
timers.append(timeRemaining)
}
func start(index: Int? = nil) {
if let index = index {
active = index
} else {
active = timers.startIndex
}
}
func stop() {
active = nil
}
func cycle() {
if let currentActive = active {
active = (currentActive + 1) % timers.count
print("active = \(active)")
} else {
active = timers.startIndex
print("(init) active = \(active)")
}
}
}
With this, timerBank.cycle() replaces your reduce.
By using the modulus operator ( % ) on Index, we could cycle through last to first without zipping.
let timers = [\Self.timer1, \.timer2, \.timer3, \.timer4]
if let onIndex = timers.firstIndex(where: { self[keyPath: $0].isActive }) {
self[keyPath: timers[onIndex]].isActive = false
let nextIndex = (onIndex + 1) % 4 // instead of 4, could use timers.count
self[keyPath: timers[nextIndex]].isActive = true
}
I got this error. Also, function range could not do this multiplication and division by g for some values that I ranged.How can i could fix this problem?
class ballShooter {
var ballShooter : Int = 0
var teta = 0 ..< 90
var v = 0 ..< 100
var g = 10
init(ballShooter:Int,teta:Int,v:Int,g:Int) {
self.ballShooter = ballShooter
self.teta = teta
self.v = v
self.g = g
}
func range(r: Int)->Int {
let r = v * v * sin(2 * teta) / g
return r
}
}
When you declare var teta = 0 ..< 90 you are telling the compiler that the variable teta (theta?) contains a half-open Int range from 0 to less than 90. Thus, your initializer needs to take a value of type Range of Int for the teta parameter.
Do you really want teta and v to contain ranges? Or are you trying to set limits to the values that callers can assign to those variables?
Edit:
If your goal is to set limits on the legal range of values for a variable, you have to implement that for yourself. Here is some sample code illustrating how you might do that:
class Foo {
// This determines the legal range of values for aVal.
public var validValRange = 50...200
public var aVal: Int {
set {
// Make sure the value is in range before updating the internal var
if validValRange ~= newValue {
_aVal = newValue
}
}
get {
// Return the previously validated value.
return _aVal
}
}
// This is a private variable that stores validated values for aVal
private var _aVal: Int = 0
}
You could test it with code like this:
let aFoo = Foo()
aFoo.aVal = 52
aFoo.aVal = 500
print(aFoo.aVal)
That would print "52" since the value 500 is out of range.
In this simple game there is a class Fighter whose purpose is to make two fighters fight. The one who looses health below 0, it looses the game.
In order to fight there is a static method fight (..) which iterates till one fighter wins the game, supported by another non static method attack (..)
object Fighter health should change as two objects fight during the game using the methods fight(...) and attack (...). The problem is it always prints the same Fighter health, and the game never ends. I don´t see where the issue is
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let david = Fighter(name: "David", health: 100, damage: 30, defense: 10, initiative: 80)
let goliath = Fighter(name: "Goliath", health: 300, damage: 60, defense: 14, initiative: 90)
let myFight1 = Fighter.fight(fighter1: david, fighter2: goliath) // always executing same Fighters
print(myFight1)
}
}
import Foundation
struct Fighter {
var name: String
var health: Double
var damage: Int
var defense: Int
var initiative: Int
init (name: String, health: Double, damage: Int, defense: Int, initiative: Int) {
self.name = name
self.health = health
self.damage = damage
self.defense = defense
self.initiative = initiative
}
init (name: String, health: Double, damage: Int, defense: Int) {
self.name = name
self.health = health
self.damage = damage
self.defense = defense
self.initiative = 0
}
static func fight(fighter1: Fighter, fighter2: Fighter) -> Fighter {
let f1 = fighter1
let f2 = fighter2
if f1.health == f2.health {
return f1
}
if f2.initiative > f1.initiative {
f2.attack(f: f1)
}
var i = 0
while f1.health > 0 {
i += 1
print("--> i: \(i)")
f1.attack(f: f2 )
if f2.health <= 0 {
return f1
}
f2.attack(f: f1)
}
return f2
}
func attack(f: Fighter) -> Void {
var g = f
g.health = g.health - Double(g.damage * (1 - g.defense / 100))
print(g)
}
}
You are using a struct for Fighter which is a value type in Swift.
The most basic distinguishing feature of a value type is that copying — the effect of assignment, initialization, and argument passing —
creates an independent instance with its own unique copy of its data
Solution: Change Fighter to a class and you are good to go.
Output of the print statements: (Second print statement changed to print(g.name, g.health))
David 70.0
--> i: 1
Goliath 240.0
David 40.0
--> i: 2
Goliath 180.0
David 10.0
--> i: 3
Goliath 120.0
David -20.0
For more reading: Value and Reference Types
After calling the method func attack(f: Fighter) -> Void every time, the properties of the Fighter who is being attacked are not getting updated. So while loop is not going to break at any point.
Please replace the code below.
static func fight(fighter1: Fighter, fighter2: Fighter) -> Fighter {
var f1 = fighter1
var f2 = fighter2
if f1.health == f2.health {
return f1
}
if f2.initiative > f1.initiative {
f1 = f2.attack(f: f1)
}
var i = 0
while f1.health > 0 {
i += 1
print("--> i: \(i)")
f2 = f1.attack(f: f2 )
if f2.health <= 0 {
return f1
}
f1 = f2.attack(f: f1)
}
return f2
}
func attack( f: Fighter) -> Fighter {
var g = f
g.health = g.health - Double(g.damage * (1 - g.defense / 100))
print(g)
return g
}
When you're saying...
var g = f
...you're actually creating a copy of that object, not a reference. So, when you're changing 'health' property, you changing it in the copy.
There are 2 simple solutions:
1) Change struct to class, 'cause classes are being referenced, unlike structs, which is just copying.
2) Replace original object with its modified copy (g)
As Rakesha notes, structs are value types, so your attack code doesn't actually modify anything:
func attack(f: Fighter) -> Void {
var g = f // Make a mutable copy of `f` called `g`
g.health = g.health - Double(g.damage * (1 - g.defense / 100)) // Modify g
print(g) // Print g
// Throw g away
}
(Side note: I think g.damage is incorrect here; I think you probably meant self.damage.)
Nothing there actually modifies f. There are several ways to address this. One is to use classes, which introduces subtle mutable state. I don't think I'd do that here. By "subtle mutable state" I mean that you expect attack to modify f, but nothing in the signature says that it does that, so the caller may be surprised.
Instead, you have several ways to implement this that make your mutations explicit on structs. You can make attack explicitly modify f:
func attack(f: inout Fighter) {
f.health = f.health - Double(damage * (1 - f.defense / 100))
}
Or you could turn it around and modify yourself when attacked by someone else:
mutating func attackedBy(f: Fighter) {
health = health - Double(f.damage * (1 - defense / 100)
}
I've got a question on property observers. There's some example code below. What I want is for the property Analysis.hasChanged to be updated to true if a.value is changed. Is there a way I can do this?
class Number {
var value: Double
init(numberValue: Double) {
self.value = NumberValue
}
}
class Analysis {
var a: Number
var hasChanged = false
init(inputNumber: Number) {
self.a = inputNumber
}
}
testNumber = Number(numberValue: 4)
testAnalysis = Analysis(inputNumber: testNumber)
print(testAnalysis.hasChanged) // will print "false"
testNumber.value = 10
print(testAnalysis.hasChanged) // will still print "false", but I want it to print "true"
In the end, I want the user to be able to be notified if any of their analyses use numbers that have been changed so that they can update the results of the analyses if they choose.
You can use the built-in property observers provided by Swift.
Every time you set a new value, the didSet will be called. You just need to attach the closure, wrapping the desired behaviour, to the Number class
class Number {
var valueDidChangeClosure: (()->())?
var value: Double {
didSet {
//won't call the valueDidChangeClosure
//if the value was changed from 10 to 10 for example..
if oldValue != value {
valueDidChangeClosure?()
}
}
}
init(numberValue: Double) {
self.value = numberValue
}
}
class Analysis {
var a: Number
var hasChanged = false
init(inputNumber: Number) {
self.a = inputNumber
self.a.valueDidChangeClosure = {
self.hasChanged = true
}
}
}
let testNumber = Number(numberValue: 4)
let testAnalysis = Analysis(inputNumber: testNumber)
print(testAnalysis.hasChanged) // will print "false"
testNumber.value = 10
print(testAnalysis.hasChanged) // will print "true"
I would do something like this, I apologize in advance if I have some syntax wrong (I usually use C/C++, think of this as more psudo code since you'd have to have a way to copy Number classes, etc.).
class Number {
var value: Double
init(numberValue: Double) {
self.value = NumberValue
}
}
class Analysis {
var a: Number
var _a: Number
bool hasChanged() {
if (a != _a) {
_a = a
return true;
}
return false;
}
init(inputNumber: Number) {
self.a = inputNumber
self._a = self.a
}
}
testNumber = Number(numberValue: 4)
testAnalysis = Analysis(inputNumber: testNumber)
print(testAnalysis.hasChanged()) // will print "false"
testNumber.value = 10
print(testAnalysis.hasChanged()) // will still print "false", but I want it to print "true"
In the end, I want the user to be able to be notified if any of their analyses use numbers that have been changed so that they can update the results of the analyses if they choose.
I don't know if this really addresses that question, I based my answer off of the code you provided. So there may be additional functionality if you want there to be some triggering method (instead of calling .hasChanged()).
Comparing doubles (and any other floating point type) with '=' or '!=' is not a good idea.
Use epsilon function instead.
Details: jessesquires.com/blog/floating-point-swift-ulp-and-epsilon/
So, I have this case where var3's value depends on both var1 and var2. Basically, this can be translated into two forms:
A) Using computed property for var3
class bla {
var var1: Int = 0
var var2: Int = 0
var var3: Int {
return var1 * var2
}
}
B) Using stored property with property observers for both var1 and var2
class bla {
var var1: Int = 0 {
didSet {
var3 = var1 * var2
}
}
var var2: Int = 0 {
didSet {
var3 = var1 * var2
}
}
var var3: Int = 0
}
However, in my case where I have to handle this, the variables are all parts of a huge model object that is being used inside a UITableViewCell. Being used in such context, it really needs to be as efficient as possible, which is the reason I'm trying to stay away from using computed properties for such case.. cause as I understand, when Swift get var3's value under A class implementation, it will compute its value on-the-fly, rather than get it cached like the way with B class implementation. Right? (Does Swift cache computed property's values in any way?)
Now I am really wondering what the best approach one should go with. In fact, the whole point I migrated some of those variables into the model object rather than computing them on-the-fly inside the cell, is to get the load off from the cell and get as much smooth scrolling as possible, which tells that using computed property makes my efforts meaningless. On the other hand, I came to cases where one of those variables depends on 3~4 other variables, which makes it necessary to re-compute it when each of those values get changed, which is also not efficient, I guess.
Any advices? Am I doing it wrong?
Here’s the approach I currently use where an instance property needs to be computed at some point and cached for subsequent accesses:
class AClassWithCachedInstanceVariables {
private var _var1: Var1Type?
var var1: Var1Type {
if let var1 = self._var1 {
// cache hit
return var1
} else {
// cache miss
let var1 = self.getVar1()
self._var1 = var1
return var1
}
}
private var _var2: Var2Type?
var var2: Var2Type {
if let var2 = self._var2 {
// cache hit
return var2
} else {
// cache miss
let var2 = self.getVar2()
self._var2 = var2
return var2
}
}
/* Extra: let’s say we also want to require var1 and var2
to be recomputed whenever this other variable is changed */
var otherVar: OtherVarType? {
didSet(oldOtherVar) {
if otherVar != oldOtherVar {
self._var1 = nil
self._var2 = nil
}
}
}
private func getVar1() -> Var1Type {
// compute stuff...
return Var1Type()
}
private func getVar2() -> Var2Type {
// compute stuff...
return Var2Type()
}
}