I copied this period of code to make a clipped shape of rounded corner. But the "rect" variable make me puzzled. It isn't an input variable.How can I use this struct without passing a value or initialize it.
import SwiftUI
struct RoundedShape:Shape{
var corners:UIRectCorner
func path(in rect:CGRect) -> Path {
let path=UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: 80, height: 80))
return Path(path.cgPath)
}
}
⬆️ This is what I got from Internet
Rectangle()
.clipShape( RoundedShape(corners: [.bottomRight]))
}
And I use this struct here and got this
image
Nothing wrong with the result,I just don't understand why this works without any passing or initializing to the "rect".
If you can help me, I appreciate it a lot.
A shape takes all available space in a place where it is created. So rect is injected by SwiftUI layout engine and in your case it is all space consumed by parent Rectangle:
Rectangle() // << bounds of this view
.clipShape( RoundedShape(corners: [.bottomRight]))
Related
Im currently making an app usig PencilKit and SwiftUI. If the screen is rotated, the canvas (and the drawing) should be rescaled acordingly. I do this with the Geometry reader and calculating the size.
NavigationView {
GeometryReader { g in
HStack {
CanvasView(canvasView: $canvasView)
.frame(width: g.size.width/1.5, height: (g.size.width/1.5)/1.5)
placeholder(placeholder: scaleDrawing(canvasHeight: (g.size.width/1.5)/1.5))
}
}
}
func scaleDrawing(canvasHeight : CGFloat) -> Bool {
let factor = canvasHeight / lastCanvasHeight
let transform = CGAffineTransform(scaleX: factor, y: factor)
canvasView.drawing = canvasView.drawing.transformed(using: transform)
lastCanvasHeight = canvasHeight
return true
}
The drawing however gets not scaled.
My solution was to create a placeholder view which has a boolean as a parameter. Also a method that scales the drawing with the height of the Canvas as an input.
This works but i think its pretty hacky and not the best solution, but this was the only solution that worked.
Is there a better way to do this? What am i missing?
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
In my project, I have a SCNView encapsulated in UIViewRepresentable, that should resize in height according to the DragGesture of another view.
The issue is that when dragging, or after drag gesture ended, the memory usage is abnormally high. Checking for memory leaks in Instruments yielded no leaks. It also only occurs in a physical device, not in Simulator.
When using the Simulator,
The memory use is stable at around 19MB, regardless of resizing or not.
When using a physical device,
The memory use spiked badly upon each resize, and doesn't fall after end. (Worst I've seen was around 1.9GB of memory usage, before crash.)
Any experts here know the reason why it happens, and how to fix it? Thanks in advance.
UPDATE: I noticed that after DragGesture ended, if I were to move the camera of the SCNView, the memory will drop back to normal values.
Heres the code of the main ContentView.
Basically its a split view layout where if I were to adjust the RoundedRectangle (which acts as a handle), the top and bottom views will resize. It is when resizing that will cause the memory spike issue, which, at worst, will cause a crash, and Message from debugger: Terminated due to memory issue.
VStack {
SceneView()
.frame(height: (UIScreen.main.bounds.height / 2) + self.gestureTranslation.height)
RoundedRectangle(cornerRadius: 5)
.frame(width: 40, height: 6)
.foregroundColor(Color.gray)
.padding(2)
.gesture(DragGesture(coordinateSpace: .global)
.onChanged({ value in
self.gestureTranslation = CGSize(width: value.translation.width + self.prevTranslation.width, height: value.translation.height + self.prevTranslation.height)
})
.onEnded({ value in
self.gestureTranslation = CGSize(width: value.translation.width + self.prevTranslation.width, height: value.translation.height + self.prevTranslation.height)
self.prevTranslation = self.gestureTranslation
})
)
Rectangle()
.fill(Color.green)
.frame(height: (UIScreen.main.bounds.height / 2) - self.gestureTranslation.height)
}
}
Heres the code for the UIViewRepresentable wrapper for the SCNView.
struct SceneView: UIViewRepresentable {
typealias UIViewType = SCNView
func makeUIView(context: Context) -> SCNView {
let scene = SCNScene(named: "SceneAssets.scnassets/Cube.scn")
let view = SCNView()
view.scene = scene
view.allowsCameraControl = true
view.defaultCameraController.interactionMode = .orbitTurntable
view.defaultCameraController.inertiaEnabled = true
return view
}
func updateUIView(_ uiView: SCNView, context: Context) {
}
}
Here's the full test project that I've made to describe the issue: https://github.com/vincentneo/swiftui-scnview-memory-issue
Say you have a Circle. How can you change its color when you hover inside the circle?
I tried
struct ContentView: View {
#State var hovered = false
var body: some View {
Circle()
.foregroundColor(hovered ? .purple : .blue)
.onHover { self.hovered = $0 }
}
}
But this causes hovered to be true even when the mouse is outside of the circle (but still inside its bounding box).
I noticed that the .onTapGesture(...) uses the hit testing of the actual shape and not the hit testing of its bounding box.
So how can we have similar hit testing behavior as the tap gesture but for hovering?
The answer depends on the precision you need. The hove in SwiftUI currently just monitor the MouseEnter and MouseExit events, so the working region for a view is the frame which is a Rectangle.
You may build a backgroundView in ZStack which can compose those Rectangle with a customized algorithm. In circle shape, it should be like a matrix with different some small rectangles. One oversimplified example could be like the following.
ZStack{
VStack{
HStack{
Rectangle().frame( width:100, height: 100).offset(x: -50, y: 0)
Rectangle().frame( width:100, height: 100).offset(x: 50, y: 0)
}}.onHover{print($0)}
Rectangle().foregroundColor(hovered ? .purple : .blue).clipShape(Circle())
}
In my project, I got messed up with two versions of app. I propably copied the project file and it created a problem. This line
myLogo.frame.size.width = UIScreen.main.bounds.width
shows, that UIScreen.main.bounds.width returns nil. How is that even possible?
fatal error: unexpectedly found nil while unwrapping an Optional value
What could be the reason and how can I repair it?
I'm working on the latest Xcode and writing in Swift 3.
Be sure myLogo is connected to your storyboard (the little dot to the left of myLogo's declaration should be dark). Then set it's frame's width property like this:
let width = UIScreen.main.bounds.width
let r = myLogo.frame
myLogo.frame = CGRect(x: r.origin.x, y: r.origin.y, width: width, height: r.height)
This would probably be better to do in Interface Builder with auto-layout so myLogo's width is constrained to the device's width.
Here's a useful extension that lets you adjust individual properties of a UIView's frame e.g.
myLogo.width = UIScreen.main.bounds.width