Fill & stroke

I'm trying to show a drawing of an aeroplane. It's fuselage is a circle, its wings come to a point and the tailplane are simple lines:

Aeroplane().stroke(Color.white, style: StrokeStyle(lineWidth:
5, lineCap: .round, lineJoin: .round)).frame(width: geometry.size.width*0.65, height: geometry.size.width*0.65).rotationEffect(.degrees(self.timePeriod.currentAngle))
Aeroplane().fill(Color.red).stroke(Color.white, style:...

Now on this call I can have .stroke or .fill but not both, this for example leads to the error 'Value of type 'some View' has no member 'stroke''

I'm baffled as to why these are not legal operators on a Path.

Hi Lantua,

The thing is both work when they are on their own. For example, fill fills but doesn’t draw the tailplane because it is but a line.


Lantua

April 20

Please read the first post, it exactly about fill or stroke, but not both. In particular that Aeroplane conforms to Path (which in turn conforms to Shape).

Or start from here:

To summarize, as is, you need to draw Aeroplane twice, once to fill and once to stroke

Thanks for your studies. I will have to think how to take it apart again!

You can use ZStack.

ZStack {
  Aeroplane().fill(Color.red)
  Aeroplane().stroke(Color.white, ...)
}

If Aeroplane is expensive to create, you can make the stack a compute variable

var aeroplane: some View {
  let path = Aeroplane()

  return ZStack {
    path.fill(Color.red)
    path.stroke(Color.white, ...)
  }
}

Be mindful of the order (as you should on any Stack).

Thank you for your advice. As the first man to create a PostScript product (1985, Mac, LaserWriter) I am baffled as to why they are mutually exclusive!

1 Like

I tried the 2nd version of yours return ZStack{ but could get it to compile. It didn't like me playing with a Path inside the view so I took your 1st suggestion of calling it twice with .fill & .stroke.

Let's hope that WWDC fixes this! Thanks again Lantua

That's weird, they should be identical. Unless, Aeroplane is sensitive to multiple init invocation, or Aeroplane is just Shape, instead of Path (nvm, that shouldn't matter).

Aeroplane is a Shape that, to my knowledge, returns a Path. It won't let me manipulate it when I have var path = Aeroplane()

Shape is a size-sensitive object that generates path. I originally though that it could affect the path since it's not created inside a view so the size parameter could be affected, then realized that it queries view size later at draw time, so it shouldn't matter.

Path does conform to Shape, too, but it simply ignores the size parameter (I believe).

I'm quite curious what you mean by

Does it simply look weird? As said, it's weird since one is just a refactoring of another.

I had this:
struct Instrument: Shape
{
@Binding var timePeriod: TimePeriod

func path(in newRect: CGRect) -> Path
{
let path: Path = Aeroplane().frame(width: 3500.65, height: 3500.65)
.rotationEffect(.degrees(timePeriod.currentAngle)) as! Path
path.fill(Color.white)
path.stroke(Color.white, style: StrokeStyle(lineWidth:
5, lineCap: .round, lineJoin: .round))
return path
}
}

I couldn't get this to compile.

What you did before was already pretty much correct. You get a Path, once you stroke it, it becomes View, same goes with fill. That's why you won't use it inside Shape, they'd cease to be Path.

I think it'd be more like

struct Instrument: View {
  let timePeriod: TimePeriod

  var body: some View {
    let path: Path = Aeroplane()

    return ZStack {
      path.fill(Color.white)
      path.stroke(Color.white, style: StrokeStyle(lineWidth: 5, lineCap: .round, lineJoin: .round))
    }
    .rotationEffect(.degrees(timePeriod.currentAngle))
    .frame(width: 350 * 0.65, height: 350 * 0.65)
  }
}

That works a treat well done, even with 30° angle of bank! Thank you some much for all of your help today. Just need to figure out how to make a publisher version of tracking the angle of the iPhone so that it displays the correct angle and its finished! Thank you again.

If publisher is the only source of information, I think you can use timePeriod as State and add another publisher:

struct Instrument<P: Publisher>: View where P.Output == TimePeriod, P.Failure == Never {
  @State timePeriod: TimePeriod = ... // Intial value before it receives the first publisher
  let publisher: P

  var body: some View {
    let path = ...
    return ZStack {
      ...
    }
    ...
    .frame(...)
    .onReceive(publisher) { newTimePeriod in
      self.timePeriod = newTimePeriod
    }
  }
}

Though I personally would prefer ObservableObject for this kind of thing.

Thank you but the Timer Publisher is working a treat and showing a countdown timer on screen. It vibrates when it recycles from zero. No the Publisher that I need to set up is to monitor "orientationChanged" so that I can activate the banking of the aeroplane live.

I see, well, I don't know too much about your struct structures to suggest further. Though I can say that, when dealing with events in SwiftUI, it's best to use on functions outlined here and attach it to the displaying View like I just did. I believe that the callback are called as long as View is drawn on the screen, though I haven't tested it extensively.

Notable ones include onReceive, onAppear, onDisappear.

I've go onReceive working for the time. I think that it would work for the bank angle if I could get the wiring to work, as the display code is already there but not being called. I must try harder!