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()
Related
I am working on a CustomForEach which would act and work like a normal ForEach in SwiftUI, this CustomForEach has it own early days and it has some issues for use for me, which makes me to learn more about SwiftUI and challenge me to try to solve the issues, one of this issues is finding a way to destroy the unneeded Views instated of rendering all needed Views!
Currently when I update lowerBound the CustomForEach starts rendering for new range which is understandable. But the new range need less Views than before and that is not understandable to rendering them again for already rendered Views.
Goal: I want find a way to stop rendering all needed Views because they are already exist and there is no need to rendering again, and just removing the unneeded Views. And also I do not want start an another expensive calculation inside CustomForEach for finding out if the Views already exist!
struct TextView: View {
let string: String
var body: some View {
print("rendering " + string)
return HStack {
Text(string)
Circle().fill(Color.red).frame(width: 5, height: 5, alignment: .center)
}
}
}
struct CustomForEachView<Content: View>: View {
private let id: Int
let range: ClosedRange<Int>
let content: (Int) -> Content
init(range: ClosedRange<Int>, #ViewBuilder content: #escaping (Int) -> Content) {
self.id = range.lowerBound
self.range = range
self.content = content
}
// The issue is rendering all existed Views when lower Bound get updated, even we do not need to render new View in updating lower Bound!
var body: some View {
content(range.lowerBound)
if let suffixRange = suffix(of: range) {
CustomForEachView(range: suffixRange, content: content)
}
}
private func suffix(of range: ClosedRange<Int>) -> ClosedRange<Int>? {
return (range.count > 1) ? (range.lowerBound + 1)...range.upperBound : nil
}
}
struct ContentView: View {
#State private var lowerBound: Int = -2
#State private var upperBound: Int = 2
var body: some View {
HStack {
CustomForEachView(range: lowerBound...upperBound) { item in
TextView(string: item.description)
}
}
HStack {
Button("add lowerBound") { lowerBound += 1 }
Spacer()
Button("add upperBound") { upperBound += 1 }
}
.padding()
}
}
First of all, one thing important thing to understand is that a SwiftUI.View struct is not a view instance that is rendered on the screen. It's merely a description of the desired view hierarchy. The SwiftUI.View instances are going to be recreated and torn down a lot by the framework anyway.
The SwiftUI framework takes care of the actual rendering. It might use UIViews for this, or it might not. That's an implementation detail you shouldn't need to worry about in most cases.
That said, you might be able to help the framework by adding explicit ids to the views by using the id modifier. That way SwiftUI can use that to keep track of which view is which.
But, I'm not sure if that would actually help. Just an idea.
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
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
let a: UIView = {
let a = UIView()
a.frame = CGRect(x: 0, y: 0, width: 20, height: 20)
return a
}()
I saw a lot of people's Swift source code defining let as this way. I just curious what is the benefit of this way?
In this case, there is no benefit, but if the variable in question were a value type then the benefit would be that you could perform some mutating setup code and still get a constant out of it.
It also lets you hide temporary variables that were only needed to initialize the constant, since they'll only exist inside the closure's scope.
So, I made a generic function in a structure which has some static methods for helping to create customized UIButtons and so on. So I did this code:
static func createAlertPicker<T: UIViewController where T: UIPickerViewDelegate, T: UIPickerViewDataSource>(#title: String, inout forPicker picker: UIPickerView, viewController: T) -> UIAlertController {
let alert = UIAlertController(title: title, message: "\n\n\n\n\n\n\n\n\n\n", preferredStyle: .Alert)
alert.view.tintColor = data.backgroundColor
picker = createPickerViewWithFrame(CGRectZero, delegate: viewController, dataSource: viewController, backgroundColor: UIColor.clearColor(), addToView: alert.view)
picker.frame = CGRect(x: 2, y: 70, width: 266, height: 162)
return alert
}
I don't get an error doing this but when calling this method in a ViewController like so:
let alert = CreatorClass.createAlertPicker(title: "select sortage", forPicker: &self.pickerView!, viewController: self)
I get a crazy error telling me Type 'UIViewController' does not conform to protocol 'UIPickerViewDelegate'. Although it is implemented, that's the ViewController's declaration:
class PlayerDetails:UIViewController, UITableViewDataSource, UITableViewDelegate, UIPickerViewDelegate, UIPickerViewDataSource
By the way, this problem only occurs in one ViewController, I'm calling it several times. Maybe it should be mentioned that this line of code (let alert = ...) is not even compiled in the first place.
I really don't understand this. Thank for any help ! :]
Yeah that error is a total lie.
The problem is with the middle parameter: &self.pickerView!.
self.pickerView is an optional. Your optional contains a reference, but unwrapping that reference passes back you back a fresh copy of the reference by value. You don’t get access to the original reference inside the optional. So when you call !, you get an immutable value. You can’t change it or assign to it, and that means you can’t pass it as an inout parameter.
Here’s a simpler example:
let i: Int? = 5
func f(inout i: Int) { i = 6 }
f(&i!) // error: 'Int' is not convertible to '#lvalue inout $T3’
This is the compiler saving you from a potentially very confusing runtime bug – if the value were passed in and changed, it would make no difference to the value you actually intended to change. Only the temporary copy would have been changed.
It might be a bit confusing because classes are reference types so you’re not used to thinking about them in value terms. But references themselves are values. What you are getting out of the unwrap is a copy of the reference, not a copy of the thing referred to.
If you change your call to something like the following, it should work:
if var picker = self.pickerView {
let alert = CreatorClass.createAlertPicker(title: "select sortage", forPicker: &picker, viewController: self)
// don’t forget to assign the value back...
self.pickerView = picker
}
This version also has the benefit of not exploding in flames if you’ve ever forgotten to set forPicker to be a value before you force unwrap it.
But if all you are using the inout for is to return a new picker (doesn’t look in your createAlertPicker like you use the value passed in, only assign to it), then why not ditch the inout and make the function return a pair of values:
static func createAlertPicker
<T: UIViewController where T: UIPickerViewDelegate>
(#title: String, viewController: T)
// return a tuple
-> (UIPickerView,UIAlertController) {
// etc…
var picker = createPickerViewWithFrame(CGRectZero, delegate: viewController, dataSource: viewController, backgroundColor: UIColor.clearColor(), addToView: alert.view)
picker.frame = CGRect(x: 2, y: 70, width: 266, height: 162)
return (picker, alert)
}
let (picker, alert) = CreatorClass.createAlertPicker(title: "select sortage”, viewController: self)
self.pickerView = picker