Working around SwiftUI Path crash in Xcode 11 beta 5 - ios

Apple broke Path in Xcode 11 beta 5:
A known issue in Xcode 11 beta 5 causes your app to crash when you use the Path structure.
So I'm trying to work around this using CGMutablePath:
var body: some View {
GeometryReader { geometry in
let path = CGMutablePath()
path.addRect(CGRect(x: 0, y: 0, width: 100, height: 100))
return Path(path)
}
}
This draws a square.
When I try to change the color as follows:
var body: some View {
GeometryReader { geometry in
let path = CGMutablePath()
path.addRect(CGRect(x: 0, y: 0, width: 100, height: 100))
return Path(path).fill(Color.purple)
}
}
I get:
Cannot convert return expression of type 'GeometryReader<_>' to return type 'some View'
Function declares an opaque return type, but has no return statements in its body from which to infer an underlying type
I'm not sure what return type to use? I tried Path but evidently fill doesn't return another Path.
I tried View but I get:
Protocol 'View' can only be used as a generic constraint because it has Self or associated type requirements
I tried some View but it didn't seem to even parse.

The iOS 13 beta 7 release notes say it's fixed in Xcode 11 beta 6, so we just have to wait for that to be released. Hopefully tomorrow!
From the release notes :
Resolved Issues
Using the Path structure no longer causes your app to crash if you’re using the SDKs included in Xcode 11 beta 6 and later. (53523206)

It still will crash... Path is too broken! But if you are curious how you can get to compile successfully, you can do something like this:
struct MyShape: View {
#State private var flag = false
var body: some View {
return GeometryReader { (geometry) -> AnyView in
let path = CGMutablePath()
path.addRect(CGRect(x: 0, y: 0, width: 100, height: 100))
return AnyView(Path(path).fill(Color.purple))
}
}
}
It will crash on Path(path)

Related

Can I generate a sequence of Views programatically in SwiftUI?

Here is a fragment of code that works for me...
VStack(spacing: pad/2) {
getRgbColor(i:0)
.onTapGesture { editColor(i:0) }
.frame(width: pad, height: pad)
.border(.black.opacity(0.5), width: 1)
getRgbColor(i:1)
.onTapGesture { editColor(i:1) }
.frame(width: pad, height: pad)
.border(.black.opacity(0.5), width: 1)
getRgbColor(i:2)
.onTapGesture { editColor(i:2) }
.frame(width: pad, height: pad)
.border(.black.opacity(0.5), width: 1)
}
The original had more entries. It worked but you can see how it repeats. I may want to make a 6x4 set of patches that looks like a Macbeth target, so I wondered whether there was a better way that did not repeat so much. I tried moving the calls into the getRgbColor() call..
func getRgbColor(i:Int) -> SwiftUI.Color {
return SwiftUI.Color(red: R + rgbDel[i][0] * step,
green: G + rgbDel[i][1] * step,
blue: B + rgbDel[i][2] * step)
.onTapGesture { editColor(i:0) }
.frame(width: pad, height: pad)
.border(.black.opacity(0.5), width: 1) as! Color
}
This compiled but gave a SIGABRT when I ran it...
Could not cast value of type
'SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI.Color,
SwiftUI.AddGestureModifier<SwiftUI._EndedGesture<SwiftUI.TapGesture>>>,
SwiftUI._FrameLayout>,
SwiftUI._OverlayModifier<SwiftUI._ShapeView<SwiftUI._StrokedShape<SwiftUI.Rectangle._Inset>,
SwiftUI.Color>>>' (0x109735e90) to 'SwiftUI.Color' (0x1ec3b9eb8).
2022-10-01 12:38:49.468445+0100 ByEye[2810:493288] Could not cast
value of type
'SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI.Color,
SwiftUI.AddGestureModifier<SwiftUI._EndedGesture<SwiftUI.TapGesture>>>,
SwiftUI._FrameLayout>,
SwiftUI._OverlayModifier<SwiftUI._ShapeView<SwiftUI._StrokedShape<SwiftUI.Rectangle._Inset>,
SwiftUI.Color>>>' (0x109735e90) to 'SwiftUI.Color' (0x1ec3b9eb8).
Is there a neat solution?
Yes there is. I hadn't imagined that calling the modifier changed the underlying type, because most of what I write is in cpp, where this is unthinkable. Following the first comment, I wrote and re-named the getRgbColor() function...
func rgbButton(i:Int) -> some View {
return SwiftUI.Color(red: R + rgbDel[i][0] * step,
green: G + rgbDel[i][1] * step,
blue: B + rgbDel[i][2] * step)
.onTapGesture { editColor(i:i) }
.frame(width: pad, height: pad)
}
Modifiers in SwiftUI don't actually modify the view. Instead, they create a new view (e.g. ModifiedContent) with the property changed/applied.
Whenever we apply a modifier to a SwiftUI view, we actually create a new view with that change applied – we don’t just modify the existing view in place.
- Hacking With Swift
So when you add all the modifiers to the Color, you are making it a different type of view. That's why as! Color fails.
To fix that, you could make a View for the rgb color.
struct RgbColor: View {
let i: Int
// Add any other info the view needs as constants
var body: some View {
// ...
}
}
To use it:
RgbColor(i: 1, /* other parameters */)
I hope this helps!

sectionHeaderTopPadding need to use below iOS 15

i want to set sectionHeaderTopPadding below iOS 15, but this property works only in ios 15. How can i set section header padding to zero below iOS 15
i have faced a gap issue that appear between table header view & nav bar. So to fix that, just add a tableHeaderView on viewDidLoad. And remember to make sure the header with a non zero height. please try the below mentioned solution might be helpful for you:
if #available(iOS 15, *) {
tableView.sectionHeaderTopPadding = 0
} else{
tableView.tableHeaderView = .init(frame: .init(x: 0, y: 0, width: 0, height: CGFloat.leastNonzeroMagnitude))
}

iOS 15: SwiftUI Canvas/TimelineView terrible performance

Playing around with new Canvas/TimelineView for iOS 15. I tried to create a particle system using the official WWDC tutorial, but couldn't manage to fix performance-related issues.
Here is the code:
struct MyView: View {
#State private var count = 32*32
var body: some View {
TimelineView(.animation) { timeline in
Canvas { context, size in
let now = timeline.date.timeIntervalSinceReferenceDate
for i in 0..<count {
context.fill(Ellipse().path(in: CGRect(x: size.width*0.01*Double(i), y: size.height*0.01*Double(i), width: 20.0, height: 20.0)), with: .color(.green))
}
}
}
}
}
It just draws 1024 circles but already consumes about 20% of the Simulator CPU and 50% of my iPhone 8 CPU. Considering the power of the iPhone and said effectiveness of new frameworks, is it expected behavior? How should I fix this if I need much more than 1024 circles?

Array.forEach creates error "Cannot convert value of type '()' to closure result type '_'"

I am trying to loop through an array in SwuftUI to render multiple text strings in different locations. Going through the array manually works, but using forEach-loop produces an error.
In the code sample below, I have commented out the manual approach (which works).
This kind of approach worked in this tutorial for drawing lines (https://developer.apple.com/tutorials/swiftui/drawing-paths-and-shapes)
(As a bonus question: is there a way to get the index/key of the individual positions through this approach, without adding that key in to the positions-Array for each of the rows?)
I have tried various approaches like ForEach, adding identified() in there as well, and adding various type definitions for the closure, but I just end up creating other errors
import SwiftUI
var positions: [CGPoint] = [
CGPoint(x: 100, y: 100),
CGPoint(x: 100, y: 200),
CGPoint(x: 100, y: 300),
]
struct ContentView : View {
var body: some View {
ZStack {
positions.forEach { (position) in
Text("Hello World")
.position(position)
}
/* The above approach produces error, this commented version works
Text("Hello World")
.position(positions[0])
Text("Hello World")
.position(positions[1])
Text("Hello World")
.position(positions[2]) */
}
}
}
#if DEBUG
struct ContentView_Previews : PreviewProvider {
static var previews: some View {
ContentView()
}
}
#endif
In the Apple tutorial you pointed to, they used the ´.forEach´ inside the Path closure, which is a "normal" closure. SwiftUI, uses a new swift feature called "function builders". The { brackets } after ZStack might look like a usual closure, but it's not!
See eg. https://www.swiftbysundell.com/posts/the-swift-51-features-that-power-swiftuis-api for more about function builders.
In essence, the "function builder" (more specifically, ViewBuilder, in this case; read more: https://developer.apple.com/documentation/swiftui/viewbuilder) get an array of all the statements in the "closure", or rather, their values. In ZStack, those values are expected to be conforming to the View protocol.
When you run someArray.forEach {...}, it will return nothing, void, also known as (). But the ViewBuilder expected something conforming to the View protocol! In other words:
Cannot convert value of type '()' to closure result type '_'
Of course it can't! Then, how might we do a loop/forEach that returns what we want?
Again, looking at the SwiftUI documentation, under "View Layout and Presentation" -> "Lists and Scroll Views", we get: ForEach, which allows us to describe the iteration declaratively, instead of imperatively looping through the positions: https://developer.apple.com/documentation/swiftui/foreach
When a view's state changes, SwiftUI regenerates the struct describing the view, compares it with the old struct, and then only makes the necessary patches to the actual UI, to save performance and allow for fancier animations, etc. To be able to do this, it needs to be able to identify each item in eg. a ForEach (eg. to distinguish an insert of a new point from just a change of an existing one). Thus, we can't just pass the array of CGPoints directly to ForEach (at least not without adding an extension to CGPoint, making them conform to the Identifiable protocol). We could make a wrapper struct:
import SwiftUI
var positions: [CGPoint] = [
CGPoint(x: 100, y: 100),
CGPoint(x: 100, y: 200),
CGPoint(x: 100, y: 300),
]
struct Note: Identifiable {
let id: Int
let position: CGPoint
let text: String
}
var notes = positions.enumerate().map { (index, position) in
// using initial index as id during setup
Note(index, position, "Point \(index + 1) at position \(position)")
}
struct ContentView: View {
var body: some View {
ZStack {
ForEach(notes) { note in
Text(note.text)
.position(note.position)
}
}
}
}
We could then add the ability to tap-and-drag the notes. When tapping a note, we might want to move it to the top of the ZStack. If any animation was playing on the note (for instance, changing its position during drag), it would normally stop (because the whole note-view would be replaced), but because the note struct now is Identifiable, SwiftUI will understand that it's only been moved, and make the change without interfering with any animation.
See https://www.hackingwithswift.com/quick-start/swiftui/how-to-create-views-in-a-loop-using-foreach or https://medium.com/#martinlasek/swiftui-dynamic-list-identifiable-73c56215f9ff for a more in depth tutorial :)
note: the code has not been tested (gah beta Xcode)
Not everything is valid in a view body. If you would like to perform a for-each loop, you need to use the special view ForEach:
https://developer.apple.com/documentation/swiftui/foreach
The view requires an identifiable array, and the identifiable array requires elements to conform to Hashable. Your example would need to be rewritten like this:
import SwiftUI
extension CGPoint: Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(x)
hasher.combine(y)
}
}
var positions: [CGPoint] = [
CGPoint(x: 100, y: 100),
CGPoint(x: 100, y: 200),
CGPoint(x: 100, y: 300),
]
struct ContentView : View {
var body: some View {
ZStack {
ForEach(positions.identified(by: \.self)) { position in
Text("Hello World")
.position(position)
}
}
}
}
Alternatively, if your array is not identifiable, you can get away with it:
var positions: [CGPoint] = [
CGPoint(x: 100, y: 100),
CGPoint(x: 100, y: 200),
CGPoint(x: 100, y: 300),
]
struct ContentView : View {
var body: some View {
ZStack {
ForEach(0..<positions.count) { i in
Text("Hello World")
.position(positions[i])
}
}
}
}

Troubleshooting UIKit display issues

I have a very strange issue where a chart (SwiftChart) is not being displayed (is not visible/rendered) when the project is built and run.
Some background:
XCode 8 (project was originally and XCode 7 project)
Objective C project/ViewControllers
A swift file to render the chart (have tried calling this from viewDidLoad & viewDidLayoutSubviews)
Storyboard containing all ui elements
Using CocoaPods
The 'print' code you see in Swift outputs 'null'
The UIView where the chart is to be rendered exists within the storyboard and is setup as follows
The code being called is:
let chart = Chart()
print(chart.window?.frame.width)
print(chart.window?.frame.height)
let data = [(x: 0.0, y: 0), (x: 3, y: 2.5), (x: 4, y: 2), (x: 5, y: 2.3), (x: 7, y: 3), (x: 8, y: 2.2), (x: 9, y: 2.5)]
let series = ChartSeries(data: data)
series.area = true
chart.xLabels = [0, 3, 6, 9, 12, 15, 18, 21, 24]
chart.xLabelsFormatter = { String(Int(round($1))) + "h" }
chart.add(series)
Visually I see nothing. I'm struggling to figure out where the problem might be so any pointers greatly appreciated
You uave to initialize it with a frame when creating it programmatically since it's a UIControl subclass. It appears their first code example is wrong but they explain it correctly in the next. All on-screen views need a frame at a minimum.
But if you already have a view of this class in your storyboard you need an outlet to point to it. Your first line of code creates a new one, so you're not talking to the one you set up, but an improperly created off-screen one.
So add an outlet and connect it to your view so you can talk to it.

Resources