Error: Escaping closure captures mutating 'self' parameter - swift

I've reviewed the other questions on this topic on SO (there are many) but none of them address this error pertaining to a #State . And I thought that this was the very problem that #State solved!
Here is the relevant code
struct ViewA: View {
#State private var startAnimation = false
let width: CGFloat
let height: CGFloat
init(width: CGFloat, height: CGFloat) {
self.width = width
self.height = height
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
startAnimation = true //<--- The error is on this line
}
}
var body: some View {
Circle()
.frame(width: width, height: height)
.scaleEffect(CGSize(width: startAnimation ? 5.0 : 1.0 , height: startAnimation ? 5.0 : 1.0))
}
}
There error is Escaping closure captures mutating 'self' parameter
Now reviewing the other SO questions related to this error lets me know that this error is related to time delayed changes ...

I don't think it has anything to do with the #State property, but with the fact that you are using an #escaping closure. An escaping closure can cause a strong reference cycle if you use self inside the closure. See for a nice article explaining #escaping closures this link.
Kind regards,
MacUserT

Related

Getting a `Type of expression is ambiguous without more context` when trying to returning a `some View` with a `ZStack` object

I'm trying to use CoreGraphics to draw some musical notes from a struct that is defined elsewhere in a swiftui App
func drawSheetMusic(in size: CGSize) -> some View {
return ZStack {
Color.clear.drawingGroup { ctx in
for note in self.musicData.notes {
let rect = CGRect(x: note.position.x - note.radius, y: note.position.y - note.radius, width: 2 * note.radius, height: 2 * note.radius)
ctx.cgContext.addEllipse(in: rect)
ctx.cgContext.setFillColor(Color.black.cgColor)
ctx.cgContext.fillPath()
}
return Rectangle().fill(Color.clear)
}
}
}
But this is returning an error Type of expression is ambiguous without more context on Line 2 there. What exactly am I doing wrong here?
func drawingGroup(opaque: Bool = false, colorMode: ColorRenderingMode = .nonLinear) -> some View
doesn't take a closure parameter. The problem becomes obvious if you remove the ZStack.
You seem to be confusing this with
UIGraphicsImageRenderer.image(actions: (UIGraphicsImageRendererContext) -> Void) -> UIImage

Memory-related crash caused by the "some" keyword in Swift

I'm getting Global buffer overflow crashes when I define SwiftUI view's with the some View type. The crashes go away when I use the exact types.
This crashes: let txt: some View = Text("Hi")
This works: let txt: Text = Text("Hi")
Any ideas? Thanks in advance.
Opaque types don't seem to be made for definitions like this. There's a lot of interesting information about this on this SO thread: What is the `some` keyword in Swift(UI)?
You can type the variable as AnyView and use AnyView() to wrap/type erase the content -- then you can use any modifiers you want.
let txt: AnyView = AnyView(Text("Hi").frame(width: 20, height: 20, alignment: .center))
I particularly like this extension to make it look a little cleaner:
extension View {
func eraseToAnyView() -> AnyView {
return AnyView(self)
}
}
let txt: AnyView = Text("Hi").frame(width: 20, height: 20, alignment: .center).eraseToAnyView()

How to use user input in function in swiftui?

I have a View, in which the user is able to enter some text into a TextField. I want to be able to get the text, which was entered in the TextField and use this value inside of a struct. The concept of the app, is that it shows the elevation degree of the sun. To be able to do this, it is scraping the values from a WebPage. However to make this app dynamic, the user has to be able to edit the url (you can change location, date etc in the url). I thought this would be fairly easy, since I only have to get some text, and edit a url before the url is being loaded. I have been able to pass the value into a view, however I need it in a struct. Maybe the whole "layout of my code is wrong, maybe I should get the data and draw the function in a view? I don't know. This is my first time coding with swift.
I want to change the latitude var.
This is my code:
View 1 (Input):
class ViewModel: ObservableObject {
#Published var latitude:String = ""
#Published var page = 0
}
struct ContentView: View {
#EnvironmentObject var value1: ViewModel
var body: some View {
if value1.page == 0{
VStack{
TextField("", text: $value1.latitude)
Button(action:{ value1.page = 1}){
Text("To next view")
}.frame(width: 300, height: 100, alignment: .center)
}
} else {
elevationGraph()
}
}
}
View 2 (Graph)
struct getHtml {
var url = URL(string: "https://midcdmz.nrel.gov/apps/spa.pl?syear=2020&smonth=1&sday=1&eyear=2020&emonth=1&eday=1&otype=0&step=60&stepunit=1&hr=12&min=0&sec=0&latitude=\(latitude)&longitude=10.757933&timezone=1.0&elev=53&press=835&temp=10&dut1=0.0&deltat=64.797&azmrot=180&slope=0&refract=0.5667&field=0")
func loadData(from url: URL?) -> String {
guard let url = url else {
return "nil"
}
let html = try! String(contentsOf: url, encoding: String.Encoding.utf8)
return html
}
}
struct elevationFunction: Shape {
var url: URL? //This only works in views, is there a way to do it in shape structs?
let html = getHtml.init().loadData(from: getHtml.init().url)
private func dbl1() -> Double {
let leftSideOfTheValue = "0:00:00,"
let rightSideOfTheValue = "\(month)/\(day)/\(year),1:00:00,"
guard let leftRange = html.range(of: leftSideOfTheValue) else {
print("cant find left range")
return 0
}
guard let rightRange = html.range(of: rightSideOfTheValue) else {
print("cant find right range")
return 0
}
let rangeOfTheValue = leftRange.upperBound..<rightRange.lowerBound
return Double(html[rangeOfTheValue].dropLast()) ?? 90
}
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint(x: 10, y: (125 - (90-dbl1()))))
path.addLine(to: CGPoint(x: 120, y: (125 - (90-45))))
path.addLine(to: CGPoint(x: 250, y: (125 - (90-dbl1()))))
var scale = (rect.height / 350) * (9/10)
var xOffset = (rect.width / 6)
var yOffset = (rect.height / 2)
return path.applying(CGAffineTransform(scaleX: scale, y: scale)).applying(CGAffineTransform(translationX: xOffset, y: yOffset))
}
}
struct elevationGraph: View {
var body: some View {
GeometryReader { geometry in
ZStack {
elevationFunction().stroke(LinearGradient(gradient: Gradient(colors: [Color.yellow, Color.red]), startPoint: .top , endPoint: .bottom), style: StrokeStyle(lineWidth: 6.0)).aspectRatio(contentMode: .fill)
}
.frame(width: 600, height: 800, alignment: .center)
}
}
}
As mentioned in my comment, you can pass a parameter to a Shape just like you can a regular View:
elevationFunction(url: yourURL)
Best practice would be to capitalize this and name it ...Shape as well:
elevationFunction becomes ElevationShape
Regarding your second question in the comment, first, you may want to fix the naming of getHtml for the same reason as above -- uncapitalized, it looks like a variable name. Maybe something like DataLoader.
Regarding the crash, you have some circular logic going on -- you call getHtml.init() and then pass a parameter that is again derived from getHtml.init() again. Why not just call getHtml() and have it loadData from its own internal URL property?
There's a larger problem at work, though, which is that you've declared html as a let property on your Shape, which is going to get recreated every time your Shape is rendered. So, on every render, with your current code, you'll create 2 new getHtmls and attempt to load the data (which very well may not actually have time to load the URL request). This very well could be blocking the first render of the Shape as well and is almost certainly causing your crash somewhere in the circular and repetitive logic going on.
Instead, you might want to consider moving your URL request to onAppear or as part of an ObservableObject where you can have a little more control of when and how often this data gets loaded. Here's a good resource on learning more about loading data using URLSession and SwiftUI: https://www.hackingwithswift.com/books/ios-swiftui/sending-and-receiving-codable-data-with-urlsession-and-swiftui

Swift protocol initializer precludes adding more stored properties to struct

TL;DR:
I want a protocol to provide default init behavior, but the compiler resists adopters adding more stored properties. I solved this with composition instead of inheritance, but what's wrong with my original approach?
Motivation
I want to automate the transformation of objects from design specifications to runtime specs. I use the example of scaling a CGSize but the intent is more general than just geometric layout. (IOW e.g. my solution won't be to adopt/reject/rewrite autolayout.)
Code
You can paste this right into a Playground, and it will run correctly.
protocol Transformable {
var size : CGSize { get } // Will be set automatically;
static var DESIGN_SPEC : CGSize { get } // could be any type.
init(size: CGSize) // Extension will require this.
}
// A simple example of transforming.
func transform(_ s: CGSize) -> CGSize {
CGSize(width: s.width/2, height: s.height/2)
}
// Add some default behavior.
// Am I sinning to want to inherit implementation?
extension Transformable {
init() { self.init(size: transform(Self.DESIGN_SPEC)) }
// User gets instance with design already transformed. No muss, fuss.
}
// Adopt the protocol...
struct T : Transformable {
let size: CGSize
static let DESIGN_SPEC = CGSize(width: 10, height: 10)
}
// ...and use it.
let t = T()
t.size // We get (5,5) as expected.
But every Eden must have its snake. I want a Transformable with another property:
struct T2 : Transformable {
// As before.
let size: CGSize
static let DESIGN_SPEC = CGSize(width: 10, height: 10)
let i : Int // This causes all sorts of trouble.
}
Whaa? Type 'T2' does not conform to protocol 'Transformable'
We have lost the synthesized initializer that sets the size member.
So... we put it back:
struct T3 : Transformable {
// As before.
let size: CGSize
static let DESIGN_SPEC = CGSize(width: 10, height: 10)
let i : Int
init(size: CGSize) {
self.size = size
self.i = 0 // But this is a hard-coded value.
}
}
But now our new member is statically determined. So we try adding another initializer:
struct T4 : Transformable {
// As before.
let size: CGSize
static let DESIGN_SPEC = CGSize(width: 10, height: 10)
let i : Int
init(size: CGSize) { self.size = size ; self.i = 0 }
// Try setting 'i':
init(i: Int) {
self.init() // Get the design spec properly transformed.
self.i = i // 'let' property 'i' may not be initialized directly;
} // use "self.init(...)" or "self = ..." instead
}
Declaring i as var shuts the compiler up. But i is immutable, and I want i that way. Explain to me why what I want is so wrong... This page is too small to include all the variations I tried, but perhaps I have missed the simple answer.

Adding New Parameters to a Child Class in Swift

I've been writing a game that has a subclass of the SKSpriteNode with some extra functions and variables. I'd like to set some of the variables when the object is created eg
let mySprite = MySubclass (width: 24, height 33)
I'm not sure this is possible which means I'll probably have to call a methos of the subclass to set the vars in a separate stage which is a bit clunky:
let mySprite = MySubclass ()
mySprite.setSize(24, height: 33)
Any ideas how I can do this in a more elegant way?
Many Thanks,
Kw
This is very fundamental OOP. Here is how you do it in Swift:
class MySubClass: SKSpriteNode {
var width: CGFloat // Declare your own properties
var height: CGFloat // ...
init(width: CGFloat, height: CGFloat) {
self.width = width // set up your own properties
self.height = height // ...
super.init() // call up to the super-class's init to set up its properties
}
}
Have you read Apple's book The Swift Programming Language? It's free and clearly covers this...