uikit core graphics in pdfkit - uikit

I'm trying to develop a graphic for my pdf but, this is the structure:
private func addRowTitle(drawContext: CGContext, offsetY: CGFloat) {
drawContext.saveGState()
drawContext.setLineWidth(2.0)
let attributes = [
NSAttributedString.Key.font: UIFont.systemFont(ofSize: 15),
NSAttributedString.Key.foregroundColor : UIColor.gray
]
drawContext.move(to: CGPoint(x: 0, y: offsetY + 20))
drawContext.addLine(to: CGPoint(x: pageRect.width, y: offsetY + 20))
drawContext.strokePath()
drawContext.restoreGState()
let rowTitles: [String] = ["Da", "A", "Annulla", "Giorni"]
let tabWidth = pageRect.width / CGFloat(4)
for titleIndex in 0..<rowTitles.count {
print("count \(rowTitles[titleIndex])")
let tabX = CGFloat(titleIndex) * tabWidth
let textRect = CGRect(x: tabX + 40, y: offsetY + 20, // top margin
width: pageRect.width ,height: 15) //pageRect.width - 40 ,height: 40
rowTitles[titleIndex].draw(in: textRect, withAttributes: attributes)
}
drawContext.move(to: CGPoint(x: 0, y: offsetY + 40))
drawContext.addLine(to: CGPoint(x: pageRect.width, y: offsetY + 40))
drawContext.strokePath()
drawContext.restoreGState()
}
and this is the second function involved
private func addRowComunication(drawContext: CGContext, offsetY: CGFloat) {
drawContext.saveGState()
drawContext.setLineWidth(2.0)
let attributes = [
NSAttributedString.Key.font: UIFont.systemFont(ofSize: 12),
NSAttributedString.Key.foregroundColor : UIColor.black
]
let rowTitles: [String] = ["25/07/2022", "27/07/2022", "10", "30"]
let tabWidth = pageRect.width / CGFloat(4)
for titleIndex in 0..<rowTitles.count {
print("count \(rowTitles[titleIndex])")
let tabX = CGFloat(titleIndex) * tabWidth
let textRect = CGRect(x: tabX + 40, y: offsetY, // top margin
width: pageRect.width ,height: 12) //pageRect.width - 40 ,height: 40
rowTitles[titleIndex].draw(in: textRect, withAttributes: attributes)
}
drawContext.move(to: CGPoint(x: 0, y: offsetY))
drawContext.addLine(to: CGPoint(x: pageRect.width, y: offsetY))
drawContext.strokePath()
drawContext.restoreGState()
}
and finally I call them from
extension PdfCreator {
func pdfData(title : String, body: String) -> Data? {
if let renderer = self.renderer {
let data = renderer.pdfData { context in
context.beginPage()
addTitle(title: title)
//addBody(body: body)
let context = context.cgContext
addWorkerRow(workerName: "Lavoratore 1", workerCF: "RGNNCL)%H!$A$%)V", offsetY: 90)
addRowTitle(drawContext: context, offsetY: 90)
addRowComunication(drawContext: context, offsetY: 100)
}
return data
}
return nil
}
}
the point is I don't understand why if I call both the functions
addRowTitle
addRowComunication
I obtain a table like the attached photo.. with a lot of white space and text is flipped for removing white space in offset I have to put 650 but the text stay flipped. I expect with a offset of 100 to be under the top row
thanks

Related

NSMenu Subclass does not respond to mouse click

I am subclassing an NSPopUpButton with the purpose of having control over the drawing methods of the button itself, but also the NSMenu that will pop up. Therefore I am also subclassing NSMenu and - most importantly - setting the view of each menu item to a custom NSView.
So far I have managed to come very close to the appearance of the original NSPopupButton and its menu. In the code, I provide a small window that will display an original NSButton on the left side and an instance of my custom version on the right.
However, the custom menu does not function properly. The following issues occur:
The button can be clicked and the menu will pop up. When the mouse is moved inside the menu, the item on which the pointer is hovering will highlight properly, except for the item that is selected: when the mouse exits its tracking area to the neighboring item, this one will be highlighted, but the first one will not lose highlight color. Only when entering the selected item again and then exiting it a second time it will lose the highlight properly.
Clicking an item will NOT dismiss the menu, the menu does not respond to any click within one of its items. The menu will however be dismissed when a click outside the menu occurs.
The button and the menu are fully functional when using the keyboard: Tab switches between the standard and the custom PopUpButton, space will summon the menu, the arrow buttons move the selection, and space or return will make a selection and dismiss the menu.
The first menu entry (Item 1) can not be selected, when dismissing the menu with enter or space when Item 1 is highlighted the Item that was selected before will stay selected.
Problem 4 is possibly unrelated, my main question is:
Why do the CustomMenuItemViews not respond to mouse events the way a stock Menu does? I assume that there is either a method that I have to override, a delegate that has to be set somewhere, or both, but I have not yet managed to find the part of the code where I have to hook in.
I was at least able to pinpoint the problem to the overridden method willOpenMenu - if I do not override, I get normal behavior, but of course, the menu will then be drawn by the cocoa method.
import Cocoa
import AppKit
#main
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
func applicationDidFinishLaunching(_ aNotification: Notification) {
window.contentViewController = MyViewController(size: NSSize(width: 200, height: 80))
}
}
class MyViewController: NSViewController {
public init(size: NSSize) {
super.init(nibName: nil, bundle: nil)
self.view = MyInnerView(frame: NSRect(x: 0, y: 0, width: size.width, height: size.height))
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class MyInnerView: NSView, NSMenuDelegate {
public override init(frame: NSRect) {
super.init(frame: frame)
let standardPopUp = NSPopUpButton(title: "Switch", target: nil, action: nil)
standardPopUp.frame = NSRect(x: 10, y: constant.buttonFrameY, width: 80, height: constant.buttonFrameHeigth)
standardPopUp.addItems(withTitles: ["Item 1", "Item 2", "Item 3"])
let popUpCell = CustomPopUpButtonCell()
let customPopUp = CustomPopUpButton(title: "Switch", target: nil, action: nil)
customPopUp.cell = popUpCell
customPopUp.menu = CustomPopUpMenu()
customPopUp.menu?.delegate = self
customPopUp.frame = NSRect(x: 90, y: constant.buttonFrameY, width: 80, height: constant.buttonFrameHeigth)
customPopUp.addItems(withTitles: ["Item 1", "Item 2", "Item 3"])
self.addSubview(standardPopUp)
self.addSubview(customPopUp)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class CustomPopUpButton: NSPopUpButton {
override func drawFocusRingMask() {
// prevent focus ring drawing
}
override func becomeFirstResponder() -> Bool {
(self.cell as! CustomPopUpButtonCell).hasFocus = true
self.needsDisplay = true
return true
}
override func resignFirstResponder() -> Bool {
(self.cell as! CustomPopUpButtonCell).hasFocus = false
self.needsDisplay = true
return true
}
// this function breaks the intended behaviour
override func willOpenMenu(_ menu: NSMenu, with event: NSEvent) {
for (index,item) in self.menu!.items.enumerated() {
item.view = MenuItemCustomView(frame: NSRect(x: 0, y: 0, width: 150, height: constant.popUpMenuCellHeigth))
item.view?.menu = menu
(item.view as! MenuItemCustomView).text = item.title
if self.indexOfSelectedItem == index {
(item.view as! MenuItemCustomView).selected = true
}
}
}
}
class CustomPopUpMenu: NSMenu {
}
class CustomPopUpButtonCell: NSPopUpButtonCell {
var hasFocus = false
override func draw(withFrame cellFrame: NSRect, in controlView: NSView) {
let context = NSGraphicsContext.current!.cgContext
// calculate width
let buttonWidth = CGFloat(60)
// draw rounded rect with shadow
let buttonRect = CGRect(x: constant.popUpButtonInset, y: (cellFrame.height/2 - constant.popUpButtonHeigth/2) - constant.popUpButtonVerticalOffset, width: buttonWidth, height: constant.popUpButtonHeigth)
let roundedRect = CGPath.init(roundedRect: buttonRect, cornerWidth: constant.popUpButtonCornerRadius, cornerHeight: constant.popUpButtonCornerRadius, transform: nil)
let shadowColor = CGColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 0.5)
context.setShadow(
offset: CGSize(width: 0, height: 0),
blur: 3.0,
color: shadowColor)
context.setLineWidth(3)
context.setFillColor(.white)
context.addPath(roundedRect)
context.fillPath()
context.setShadow(offset: CGSize(), blur: 0)
// draw arrow rect
let arrowRect = CGRect(x: constant.popUpButtonInset + buttonWidth - constant.popUpButtonArrowRectWidth - constant.popUpButtonArrowRectGap, y: (cellFrame.height/2 - constant.popUpButtonArrowRectWidth/2 - constant.popUpButtonVerticalOffset), width: constant.popUpButtonArrowRectWidth, height: constant.popUpButtonArrowRectWidth)
let arrowRoundedRect = CGPath.init(roundedRect: arrowRect, cornerWidth: constant.popUpButtonArrowRectCornerRadius, cornerHeight: constant.popUpButtonArrowRectCornerRadius, transform: nil)
context.setFillColor(NSColor.controlAccentColor.cgColor)
context.addPath(arrowRoundedRect)
context.fillPath()
// draw arrows
context.setStrokeColor(.white)
context.setLineWidth(1.5)
context.setLineCap(.round)
context.move(to: CGPoint(x: constant.popUpButtonInset + buttonWidth - constant.popUpButtonArrowRectWidth - constant.popUpButtonArrowRectGap + 5, y: (cellFrame.height/2 - constant.popUpButtonVerticalOffset + 2)))
context.addLine(to: CGPoint(x: constant.popUpButtonInset + buttonWidth - constant.popUpButtonArrowRectWidth/2 - constant.popUpButtonArrowRectGap, y: (cellFrame.height/2 - constant.popUpButtonVerticalOffset + constant.popUpButtonArrowRectWidth/2 - 3)))
context.addLine(to: CGPoint(x: constant.popUpButtonInset + buttonWidth - constant.popUpButtonArrowRectGap - 5, y: (cellFrame.height/2 - constant.popUpButtonVerticalOffset + 2)))
context.strokePath()
context.move(to: CGPoint(x: constant.popUpButtonInset + buttonWidth - constant.popUpButtonArrowRectWidth - constant.popUpButtonArrowRectGap + 5, y: (cellFrame.height/2 - constant.popUpButtonVerticalOffset - 2)))
context.addLine(to: CGPoint(x: constant.popUpButtonInset + buttonWidth - constant.popUpButtonArrowRectWidth/2 - constant.popUpButtonArrowRectGap, y: (cellFrame.height/2 - constant.popUpButtonVerticalOffset - constant.popUpButtonArrowRectWidth/2 + 3)))
context.addLine(to: CGPoint(x: constant.popUpButtonInset + buttonWidth - constant.popUpButtonArrowRectGap - 5, y: (cellFrame.height/2 - constant.popUpButtonVerticalOffset - 2)))
context.strokePath()
// draw text
let textColor: NSColor = .black
let attributes = [
NSAttributedString.Key.font : NSFont(name: "Lucida Grande", size: CGFloat(12)),
NSAttributedString.Key.foregroundColor : textColor
]
let textPosition = NSPoint(x: constant.popUpButtonInset + constant.popUpButtonArrowRectGap, y: constant.popUpButtonVerticalOffset + 8 - constant.popUpButtonArrowRectGap)
NSAttributedString(string: self.selectedItem!.title, attributes: attributes as [NSAttributedString.Key : Any]).draw(at: textPosition)
if hasFocus {
let buttonRect = CGRect(x: constant.popUpButtonInset - constant.popUpButtonFocusRingThickness/4, y: (cellFrame.height/2 - constant.popUpButtonHeigth/2) - constant.popUpButtonVerticalOffset - constant.popUpButtonFocusRingThickness/4, width: buttonWidth + constant.popUpButtonFocusRingThickness*0.5, height: constant.popUpButtonHeigth + constant.popUpButtonFocusRingThickness*0.5)
let roundedRect = CGPath.init(roundedRect: buttonRect, cornerWidth: constant.popUpButtonFocusRingCornerRadius, cornerHeight: constant.popUpButtonFocusRingCornerRadius, transform: nil)
context.setLineWidth(constant.popUpButtonFocusRingThickness)
context.setStrokeColor((NSColor.keyboardFocusIndicatorColor).cgColor)
context.addPath(roundedRect)
context.strokePath()
}
}
}
class MenuItemCustomView: NSView {
var text: String = ""
var scaleFactor: CGFloat = 1
var selected = false
override func draw(_ dirtyRect: NSRect) {
let context = NSGraphicsContext.current!.cgContext
context.setLineWidth(1)
var textColor: NSColor
if self.enclosingMenuItem!.isHighlighted {
textColor = .white
context.setStrokeColor(.white
)
// draw selection frame
let arrowRect = CGRect(x: constant.popUpMenuSelectionInset, y: 0, width: (self.frame.width - constant.popUpMenuSelectionInset*2), height: self.frame.height)
let arrowRoundedRect = CGPath.init(roundedRect: arrowRect, cornerWidth: constant.popUpButtonArrowRectCornerRadius, cornerHeight: constant.popUpButtonArrowRectCornerRadius, transform: nil)
context.setFillColor(NSColor.controlAccentColor.cgColor)
context.addPath(arrowRoundedRect)
context.fillPath()
} else {
textColor = .black
context.setStrokeColor(.black)
}
let attributes = [
NSAttributedString.Key.font : NSFont(name: "Lucida Grande", size: CGFloat(12*scaleFactor)),
NSAttributedString.Key.foregroundColor : textColor
]
let textPosition = NSPoint(x: constant.popUpMenuTextX*scaleFactor, y: constant.popUpMenuTextY*scaleFactor)
NSAttributedString(string: self.text, attributes: attributes as [NSAttributedString.Key : Any]).draw(at: textPosition)
if selected {
// draw checkmark
context.setLineWidth(2*scaleFactor)
let inset = constant.popUpMenuSelectionInset
context.move(to: CGPoint(x: (inset + 3)*scaleFactor, y: (self.frame.height/2)))
context.addLine(to: CGPoint(x: (inset + 7)*scaleFactor, y: self.frame.height*0.3))
context.addLine(to: CGPoint(x: (inset + 13)*scaleFactor, y: (self.frame.height*0.7)))
context.strokePath()
}
}
}
struct constant {
static let popUpButtonHeigth = CGFloat(20)
static let popUpButtonInset = CGFloat(4)
static let popUpButtonCornerRadius = CGFloat(5)
static let popUpButtonVerticalOffset = CGFloat(1.5)
static let popUpButtonFocusRingThickness = CGFloat(4)
static let popUpButtonFocusRingCornerRadius = CGFloat(6)
static let popUpButtonArrowRectWidth = CGFloat(15)
static let popUpButtonArrowRectGap = CGFloat(2)
static let popUpButtonArrowRectCornerRadius = CGFloat(3)
static let popUpMenuCellHeigth = CGFloat(24)
static let popUpMenuTextX = CGFloat(25)
static let popUpMenuTextY = CGFloat(4)
static let popUpMenuSelectionInset = CGFloat(5)
static let buttonFrameY = CGFloat(10)
static let buttonFrameHeigth = CGFloat(35)
}

How to move an object along a path in swiftui

I try to move an Image along a path in a vein. I didn't find how to do it. Here my present code:
struct ContentView: View {
#State var chemin = Path {chemin in
chemin.move(to: CGPoint(x: 620, y: 0))
chemin.addLine(to: CGPoint(x:460, y:120))
chemin.addQuadCurve(to: CGPoint(x: 480, y: 200), control: CGPoint(x: 380, y: 220))
chemin.addLine(to: CGPoint(x:590, y:120))
chemin.addQuadCurve(to: CGPoint(x: 680, y: 320), control: CGPoint(x: 890, y: 160))
chemin.addLine(to: CGPoint(x: 300, y: 330))
chemin.addLine(to: CGPoint(x: 0, y: 160))
}
var body: some View {
ZStack{
Image("carte").resizable().ignoresSafeArea()
VStack{
Image("corona").resizable().frame(width: 150, height: 100, alignment: .center).animation(Animation.linear(duration: 3), value: true)
}
chemin.stroke(Color.green, lineWidth: 3).ignoresSafeArea()
}
}
}
I finally found how to do it, here's the code with some extra-functions:
import SwiftUI
struct FollowEffect: GeometryEffect {
var pct: CGFloat = 0
let path: Path
var rotate = true
var animatableData: CGFloat {
get { return pct }
set { pct = newValue }
}
func effectValue(size: CGSize) -> ProjectionTransform {
if !rotate {
let pt = percentPoint(pct)
return ProjectionTransform(CGAffineTransform(translationX: pt.x, y: pt.y))
} else {
// Calculate rotation angle, by calculating an imaginary line between two points
// in the path: the current position (1) and a point very close behind in the path (2).
let pt1 = percentPoint(pct)
let pt2 = percentPoint(pct - 0.01)
let a = pt2.x - pt1.x
let b = pt2.y - pt1.y
let angle = a < 0 ? atan(Double(b / a)) : atan(Double(b / a)) - Double.pi
let transform = CGAffineTransform(translationX: pt1.x, y: pt1.y).rotated(by: CGFloat(angle))
return ProjectionTransform(transform)
}
}
func percentPoint(_ percent: CGFloat) -> CGPoint {
let pct = percent > 1 ? 0 : (percent < 0 ? 1 : percent)
let f = pct > 0.999 ? CGFloat(1-0.001) : pct
let t = pct > 0.999 ? CGFloat(1) : pct + 0.001
let tp = path.trimmedPath(from: f, to: t)
return CGPoint(x: tp.boundingRect.midX, y: tp.boundingRect.midY)
}
}
struct ContentView: View {
#State var chemin: Path = Path {path in
path.move(to: CGPoint(x: 620, y: 0))
path.addLine(to: CGPoint(x:460, y:120))
path.addQuadCurve(to: CGPoint(x: 480, y: 200), control: CGPoint(x: 380, y: 220))
path.addLine(to: CGPoint(x:590, y:120))
path.addQuadCurve(to: CGPoint(x: 680, y: 320), control: CGPoint(x: 890, y: 160))
path.addLine(to: CGPoint(x: 300, y: 330))
path.addLine(to: CGPoint(x: 0, y: 160))
}
#State private var flag = false
var body: some View {
GeometryReader { proxy in
ZStack(alignment: .topLeading) {
Image("carte").resizable().ignoresSafeArea()
// Animate movement of Image
Image("corona").resizable().foregroundColor(Color.red).frame(width: 50, height: 50).offset(x: -25, y: -25).modifier(FollowEffect(pct: self.flag ? 1 : 0, path: chemin)).onAppear {
withAnimation(Animation.linear(duration: 4.0).repeatForever(autoreverses: false)) {
self.flag.toggle()
}
}
}.frame(alignment: .topLeading)
}
}
}

In SpriteKit how can I have code for tracking the textures of two nodes at a time? As well code to track if all the nodes were matched in time?

I would like to have one piece of code in my app where I can track the textures of two nodes at a time. If the textures of the two nodes don't match I would like them reset back to their original state before the user touched them. On top of that i would like another piece of code for tracking if the particular node's textures were matched under a particular time frame like 90 seconds. Any tips on how I can do something like this for my app? Thanks.
Here is my current code:
import Foundation
import SpriteKit
class EasyScreen: SKScene {
override func didMove(to view: SKView) {
var background = SKSpriteNode(imageNamed: "Easy Screen Background")
let timerText = SKLabelNode(fontNamed: "Arial")
timerText.fontSize = 40
timerText.fontColor = SKColor.white
timerText.position = CGPoint(x: 20, y: 400)
timerText.zPosition = 1
var counter:Int = 90
timerText.run(SKAction.repeatForever(SKAction.sequence([SKAction.run {
counter-=1
timerText.text = " Time: \(counter)"
print("\(counter)")
if counter <= 0{
let newScene = TryAgainScreen(fileNamed: "Try Again Screen")
newScene?.scaleMode = .aspectFill
self.view?.presentScene(newScene)
}
},SKAction.wait(forDuration: 1)])))
background.position = CGPoint(x: 0, y: 0)
background.size.width = self.size.width
background.size.height = self.size.height
background.anchorPoint = CGPoint(x: 0.5,y: 0.5)
let matchCardOne = SKSpriteNode(imageNamed: "Fruit Match Card")
let matchCardTwo = SKSpriteNode(imageNamed: "Fruit Match Card")
let matchCardThree = SKSpriteNode(imageNamed: "Fruit Match Card")
let matchCardFour = SKSpriteNode(imageNamed: "Fruit Match Card")
matchCardOne.name = "FruitMatchCard1"
matchCardTwo.name = "FruitMatchCard2"
matchCardThree.name = "FruitMatchCard3"
matchCardFour.name = "FruitMatchCard4"
matchCardOne.size = CGSize(width: 150, height: 300)
matchCardTwo.size = CGSize(width: 150, height: 300)
matchCardThree.size = CGSize(width: 150, height: 300)
matchCardFour.size = CGSize(width: 150, height: 300)
matchCardOne.zPosition = 1
matchCardTwo.zPosition = 1
matchCardThree.zPosition = 1
matchCardFour.zPosition = 1
matchCardOne.anchorPoint = CGPoint(x: 0.5, y: 0.5)
matchCardTwo.anchorPoint = CGPoint(x: 0.5, y: 0.5)
matchCardThree.anchorPoint = CGPoint(x: 0.5, y: 0.5)
matchCardFour.anchorPoint = CGPoint(x: 0.5, y: 0.5)
matchCardOne.position = CGPoint(x: -125, y: 60)
matchCardTwo.position = CGPoint(x: -125, y: -260)
matchCardThree.position = CGPoint(x: 70, y: 60)
matchCardFour.position = CGPoint(x: 70 , y: -260)
addChild(background)
addChild(matchCardOne)
addChild(matchCardTwo)
addChild(matchCardThree)
addChild(matchCardFour)
addChild(timerText)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view?.isMultipleTouchEnabled = false
let touch = touches.first
let positionInSceneOne = touch!.location(in: self)
let tappedNodes = nodes(at: positionInSceneOne)
for node in tappedNodes{
if let tappedCard = node as? SKSpriteNode {
if tappedCard.name == "FruitMatchCard1" {
tappedCard.texture = SKTexture(imageNamed: "Apple")
}
}
let touchTwo = touches.first
let positionInSceneTwo = touch!.location(in: self)
let tappedNodesTwo = nodes(at: positionInSceneTwo)
for node in tappedNodesTwo{
if let tappedCard = node as? SKSpriteNode {
if tappedCard.name == "FruitMatchCard2" {
tappedCard.texture = SKTexture(imageNamed: "Apple")
}
}
let touchThree = touches.first
let positionInSceneThree = touch!.location(in: self)
let tappedNodesThree = nodes(at: positionInSceneThree)
for node in tappedNodesThree{
if let tappedCard = node as? SKSpriteNode {
if tappedCard.name == "FruitMatchCard3" {
tappedCard.texture = SKTexture(imageNamed: "Grapes")
}
}
let touchFour = touches.first
let positionInSceneFour = touch!.location(in: self)
let tappedNodesFour = nodes(at: positionInSceneFour)
for node in tappedNodesFour{
if let tappedCard = node as? SKSpriteNode {
if tappedCard.name == "FruitMatchCard4" {
tappedCard.texture = SKTexture(imageNamed: "Grapes")
}
}
}
}
}
}
}
}

Round Specific Corners in a SwiftUI Mac App

I've been trying to figure out how to round specific corners of a SwiftUI View in a Mac app. All the solutions I can find (such as this) apply only to iOS since it has UIRectCorner. There is no NSRectCorner equivalent that I can find.
Back in the day, I would round a specific corner of an NSView like this:
layer?.cornerRadius = 5
layer?.maskedCorners = .layerMinXMinYCorner //Bottom-left corner
Has anyone found a way to round specific corners in a Mac app in SwiftUI?
and here comes the answer for macOS :)
// defines OptionSet, which corners to be rounded – same as UIRectCorner
struct RectCorner: OptionSet {
let rawValue: Int
static let topLeft = RectCorner(rawValue: 1 << 0)
static let topRight = RectCorner(rawValue: 1 << 1)
static let bottomRight = RectCorner(rawValue: 1 << 2)
static let bottomLeft = RectCorner(rawValue: 1 << 3)
static let allCorners: RectCorner = [.topLeft, topRight, .bottomLeft, .bottomRight]
}
// draws shape with specified rounded corners applying corner radius
struct RoundedCornersShape: Shape {
var radius: CGFloat = .zero
var corners: RectCorner = .allCorners
func path(in rect: CGRect) -> Path {
var path = Path()
let p1 = CGPoint(x: rect.minX, y: corners.contains(.topLeft) ? rect.minY + radius : rect.minY )
let p2 = CGPoint(x: corners.contains(.topLeft) ? rect.minX + radius : rect.minX, y: rect.minY )
let p3 = CGPoint(x: corners.contains(.topRight) ? rect.maxX - radius : rect.maxX, y: rect.minY )
let p4 = CGPoint(x: rect.maxX, y: corners.contains(.topRight) ? rect.minY + radius : rect.minY )
let p5 = CGPoint(x: rect.maxX, y: corners.contains(.bottomRight) ? rect.maxY - radius : rect.maxY )
let p6 = CGPoint(x: corners.contains(.bottomRight) ? rect.maxX - radius : rect.maxX, y: rect.maxY )
let p7 = CGPoint(x: corners.contains(.bottomLeft) ? rect.minX + radius : rect.minX, y: rect.maxY )
let p8 = CGPoint(x: rect.minX, y: corners.contains(.bottomLeft) ? rect.maxY - radius : rect.maxY )
path.move(to: p1)
path.addArc(tangent1End: CGPoint(x: rect.minX, y: rect.minY),
tangent2End: p2,
radius: radius)
path.addLine(to: p3)
path.addArc(tangent1End: CGPoint(x: rect.maxX, y: rect.minY),
tangent2End: p4,
radius: radius)
path.addLine(to: p5)
path.addArc(tangent1End: CGPoint(x: rect.maxX, y: rect.maxY),
tangent2End: p6,
radius: radius)
path.addLine(to: p7)
path.addArc(tangent1End: CGPoint(x: rect.minX, y: rect.maxY),
tangent2End: p8,
radius: radius)
path.closeSubpath()
return path
}
}
// View extension, to be used like modifier:
// SomeView().roundedCorners(radius: 20, corners: [.topLeft, .bottomRight])
extension View {
func roundedCorners(radius: CGFloat, corners: RectCorner) -> some View {
clipShape( RoundedCornersShape(radius: radius, corners: corners) )
}
}
Create a custom shape
import SwiftUI
struct RoundCorner: Shape {
// MARK: - PROPERTIES
var cornerRadius: CGFloat
var maskedCorners: UIRectCorner
// MARK: - PATH
func path(in rect: CGRect) -> Path {
let path = UIBezierPath(roundedRect: rect, byRoundingCorners: maskedCorners, cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))
return Path(path.cgPath)
}
}
// MARK: - PREVIEW
struct RoundCorner_Previews: PreviewProvider {
static var previews: some View {
RoundCorner(cornerRadius: 20, maskedCorners: .allCorners)
}
}
And Apply this shape to your view
import SwiftUI
struct SwiftUIView: View {
// MARK: - BODY
var body: some View {
Text("Hello, World!")
.padding()
.background(
Color.orange
)
.clipShape(
RoundCorner(
cornerRadius: 12,
maskedCorners: [.topLeft, .bottomRight]
)//OUR CUSTOM SHAPE
)
}
}
// MARK: - PREVIEW
struct SwiftUIView_Previews: PreviewProvider {
static var previews: some View {
SwiftUIView()
}
}

SwiftUI + MacOS + Xcode: Format right side of two view HStack such that it is centered in its parent view

I have a view that contains an HStack that contains two views. The right view contains a graph. The left view contains labels for the graphs YAxis. I would like to center the graph in its parent view. If someone could point me in the right direction to accomplish this it would be appreciated. Below is my code.
Regards,
Chris
import SwiftUI
struct MainView: View {
#EnvironmentObject var viewModel : ViewModel
var body: some View {
VStack (alignment: .center) {
let maxYValue = viewModel.data.max { $0.Value < $1.Value }?.Value
let minYValue = viewModel.data.max { $0.Value > $1.Value }?.Value
let deltaY = maxYValue! - minYValue!
let maxYVal = Int(round(maxYValue!))
let minYVal = Int(round(minYValue!))
HStack{
VStack{
Text(String("\(maxYVal)"))
Spacer()
Text("2")
Spacer()
Text("3")
Spacer()
Text("3")
Spacer()
Text(String("\(minYVal)"))
}.frame(width: 100, height: 510, alignment: .trailing)
GeometryReader { geometry in
Path { path in
for index in viewModel.data.indices {
let xPosition = ((geometry.size.width) / (CGFloat(viewModel.data.count - 1))) * CGFloat(index + 0)
let yPosition = (1 - (CGFloat(viewModel.data[index].Value - minYValue!)) / CGFloat(deltaY) ) * (geometry.size.height)
if index == 0 {
path.move(to: CGPoint(x : xPosition, y : yPosition))
}
path.addLine(to: CGPoint(x : xPosition, y: yPosition))
}
} .stroke(Color.blue, style: StrokeStyle(lineWidth: 2, lineCap: .round, lineJoin: .round))
Path { path in
path.move(to: CGPoint(x: 0, y: geometry.size.height))
path.addLine(to: CGPoint(x: 0, y: 0))
path.move(to: CGPoint(x: 0, y: geometry.size.height))
path.addLine(to: CGPoint(x: geometry.size.width, y: geometry.size.height))
}.stroke(Color.black, style: StrokeStyle(lineWidth: 2))
Path { path in
for index in 0...3 {
path.move(to: CGPoint(x: 0 , y: geometry.size.height * CGFloat(index) / 4))
path.addLine(to: CGPoint(x: geometry.size.width , y: geometry.size.height * CGFloat(index) / 4))
}
}.stroke(Color.gray, style: StrokeStyle(lineWidth: 0.5))
} // end geometry reader
.frame(width: 700, height: 500)// end inner geometry reader
}
} // end of vstack
}
}
Thank you for the response. After posting my query I did some research and came to the conclusion that my code was too complex. So, I rewrote it and I think simplified it. I create a geometry reader and use it to create two rectangles that combined take up the entire parent view. In the upper rectangle I place the graph title and in the lower rectangle I place the graph and am in the process of putting labels on the X and Y axis. Using this approach has allowed me to exactly place items within the rectangle including centering the graph. Problem solved.
The only thing I need to figure out is how to change the stroke for a given line without having to create multiple paths as I have done.
Below is the updated code.
Thanks again for responding.
Chris
import SwiftUI
struct MainView: View {
var body: some View {
GeometryReader { geometry in
VStack (spacing: 0){
let frameWidth = geometry.size.width
let frameHeight = geometry.size.height
Rectangle()
.fill(Color.clear)
// .border(Color.red)
.frame(width: frameWidth, height: frameHeight * 1/8 )
.overlay(
Text("Principal Values Over the Last Year")
.font(.largeTitle)
)
Rectangle()
.fill(Color.clear)
.frame(width: frameWidth, height: frameHeight * 7/8 )
// .border(Color.black, width: 1)
.overlay(
PrincipalChart(frameWidth: frameWidth, frameHeight: frameHeight * 7/8)
)
.padding(0)
} // end Vstack
} // end geometry reader
} // end body var
} // end of MainView
struct PrincipalChart: View {
let frameWidth : CGFloat
let frameHeight : CGFloat
#EnvironmentObject var viewModel : ViewModel
var body: some View {
ZStack {
let maxYValue = viewModel.data.max { $0.Value < $1.Value }?.Value
let minYValue = viewModel.data.max { $0.Value > $1.Value }?.Value
let deltaY = maxYValue! - minYValue!
let maxYVal = Int(round(maxYValue!))
let minYVal = Int(round(minYValue!))
let yAxisStep = Int((maxYVal - minYVal) / 4)
Path { path in
for index in viewModel.data.indices {
let xPosition = (frameWidth * 1/10) + ((frameWidth * 8/10) / (CGFloat(viewModel.data.count - 1))) * CGFloat(index + 0)
let yPosition = (1 - (CGFloat(viewModel.data[index].Value - minYValue!)) / CGFloat(deltaY) ) * (frameHeight * 9/10)
if index == 0 {
path.move(to: CGPoint(x : xPosition, y : yPosition))
}
path.addLine(to: CGPoint(x : xPosition, y: yPosition))
}
} .stroke(Color.blue, style: StrokeStyle(lineWidth: 2, lineCap: .round, lineJoin: .round))
Path { path in
path.move(to: CGPoint(x: frameWidth * 1/10, y: 0))
path.addLine(to: CGPoint(x: frameWidth * 1/10 , y: frameHeight * 9/10 ))
path.addLine(to: CGPoint(x: frameWidth * 9/10, y: frameHeight * 9/10))
}.stroke(Color.black, style: StrokeStyle(lineWidth: 2))
Path { path in
for index in 0...3 {
path.move(to: CGPoint(x: frameWidth * 1/10, y: (frameHeight * 9/10) * (CGFloat (index) / 4) ))
path.addLine(to: CGPoint(x: frameWidth * 9/10, y: (frameHeight * 9/10) * (CGFloat (index) / 4)))
}
}.stroke(Color.gray, style: StrokeStyle(lineWidth: 0.5))
Text("\(maxYVal)").position(x: (frameWidth * 1/10) - 20, y: 1)
let stepValue3 = maxYVal - yAxisStep
Text("\(stepValue3)").position(x: (frameWidth * 1/10) - 20, y: -2 + (frameHeight * 9/10) * 1 / 4 )
let stepValue2 = stepValue3 - yAxisStep
Text("\(stepValue2)").position(x: (frameWidth * 1/10) - 20, y: -2 + (frameHeight * 9/10) * 2 / 4 )
let stepValue1 = stepValue2 - yAxisStep
Text("\(stepValue1)").position(x: (frameWidth * 1/10) - 20, y: -2 + (frameHeight * 9/10) * 3 / 4 )
Text("\(minYVal)").position(x: (frameWidth * 1/10) - 20, y: -2 + (frameHeight * 9/10) * 4 / 4 )
} // end z stack
} // end body var
} // end Principal Chart view

Resources