I am creating a poly line graphic to add to a sceneView for a SwiftUI app. Is there any way to add a text label along this line?
Ideally I would like a box with some text inside it along the direction of the line in the middle of the line if that's achievable.
let polyline = Polyline(
points: [
point1,
point2
]
)
let polylineSymbol = SimpleLineSymbol(style: .solid, color: .cyan, width: 2.0)
let polylineGraphic = Graphic(geometry: polyline, symbol: polylineSymbol)
Solved! Go to Solution.
Please see the code snippet below. The key here is to set
overlay.labelsAreEnabled = true
so that the overlay allows displaying labels.
import SwiftUI
import ArcGIS
class Model: ObservableObject {
let lineOverlay: GraphicsOverlay = {
let textSymbol = TextSymbol(color: .blue, size: 12)
let expression = SimpleLabelExpression(simpleExpression: "[Name]")
let labelDefinition = LabelDefinition(labelExpression: expression, textSymbol: textSymbol)
labelDefinition.placement = .lineCenterAlong
let overlay = GraphicsOverlay()
overlay.labelsAreEnabled = true
overlay.addLabelDefinition(labelDefinition)
return overlay
}()
}
struct ContentView: View {
@StateObject private var model = Model()
@State private var scene = Scene(basemapStyle: .arcGISLightGray)
@State private var camera: Camera? = Camera(location: Point(x: 113.0, y: 22.0, z: 4_953_394, spatialReference: .wgs84), heading: 360.0, pitch: 0.0, roll: 0)
func addGraphic(latitude: Double, longitude: Double) {
let polyline = Polyline(
points: [
Point(latitude: latitude, longitude: longitude),
Point(x: 115, y: 24)
]
)
let polylineSymbol = SimpleLineSymbol(style: .solid, color: .cyan, width: 2.0)
let polylineGraphic = Graphic(geometry: polyline, attributes: ["Name": "Hello World"], symbol: polylineSymbol)
model.lineOverlay.addGraphic(polylineGraphic)
}
var body: some View {
SceneView(scene: scene, camera: $camera, graphicsOverlays: [model.lineOverlay])
.onAppear {
addGraphic(latitude: 39, longitude: 120)
}
}
}
Here's a sample that shows how to setup labels for a layer (albeit a polygon layer in this case, but the pattern is basically the same): Show labels on layer.
And here's the full technical Labeling properties manual that provides detailed information on how the properties that you see in ArcGIS Pro map onto API types exposed in Swift.
Even if you don't currently have access to the ArcGIS Pro desktop software, I recommend looking at the documentation to determine the properties that will help you achieve the effect you're looking for in the API.
For placing a label "along the direction of the line in the middle of the line" you probably want to use 1.14.0 placement property value `lineCenterAlong` which means Center of label prefers the midpoint of the geometry, label follows the geometry segments. And the symbol is determined by the TextSymbol | Documentation you specify.
Hi. Thanks for the assistance. I've tried to add this using just some example text because I haven't figured out the $feature. yet but the example text is not displayed on the poly lines in my layer. Using this method am I able to add labels to a GraphicsLayer consisting of Polyline Graphics?
In the code below myOverlay is a GraphicsOverlay which I have added graphics to that have polyline geometry and SimpleLineSymbol. The graphicsOverlay appears but without the Text Test above each line.
let textSymbol = TextSymbol(color: .blue, size: 12)
let expression = "Test"
let arcadeLabelExpression = ArcadeLabelExpression(arcadeString: expression)
let labelDefinition = LabelDefinition(labelExpression: arcadeLabelExpression, textSymbol: textSymbol)
labelDefinition.placement = .lineCenterAlong
myOverlay.addLabelDefinition(labelDefinition)
If you want to display arbitrary text, then a simple label expression may be better initially: SimpleLabelExpression | Documentation (arcgis.com)
For ArcadeLabelExpressions I recommend browsing the doc Introduction | ArcGIS Arcade | ArcGIS Developers and then also trying out the Playground | ArcGIS Arcade | ArcGIS Developers. Essentially Arcade enables you to create label expressions that are derived from attributes in the data and can include more advanced behaviors such as calculations and formatting.
Thanks. I tried the SimpleLabelExpression as well but nothing is appearing. Not sure if the problem is that these only work with FeatureLayers and not GraphicOverlays or maybe my Lite Licence does not allow this.
Labels should work for both Features and Graphics and at the Lite license level.
Can you share your full code snippet?
This is a sample project. I guess I must be doing something fundamentally wrong because when I place this view in a NavigationSplitView crashes right away. But without the NavigationSplitView it works fine. In this example the line is drawn but there is not text on the line.
import SwiftUI
import ArcGIS
class ViewModel: ObservableObject {
@Published var lineOverlay = GraphicsOverlay()
}
struct GeographyView: View {
@StateObject var viewModel = ViewModel()
@State private var scene = Scene()
@State private var mobileScenePackage: MobileScenePackage!
@State var camera: Camera? = Camera(location: Point(x: 113.0, y: 22.0, z: 4_953_394, spatialReference: .wgs84), heading: 360.0, pitch: 0.0, roll: 0)
private func loadMobileScenePackage() async throws {
let sceneURL = Bundle.main.url(forResource: "satteliteImageryScene", withExtension: "mspk")!
self.mobileScenePackage = MobileScenePackage(fileURL: sceneURL)
try await mobileScenePackage.load()
guard let scene = mobileScenePackage.scenes.first else { return }
let x = 113.0
let y = 22.0
scene.initialViewpoint = Viewpoint(latitude: y, longitude: x, scale: 4_953_394)
addOverlay(x: x, y: y)
self.scene = scene
}
func addOverlay(x: Double, y: Double) {
let overlay = GraphicsOverlay()
let point1 = Point(x: x, y: y, spatialReference: .wgs84)
let point2 = Point(x: 115, y: 24)
let polyline = Polyline(
points: [
point1,
point2
]
)
let polylineSymbol = SimpleLineSymbol(style: .solid, color: .cyan, width: 2.0)
let polylineGraphic = Graphic(geometry: polyline, symbol: polylineSymbol)
viewModel.lineOverlay.addGraphic(polylineGraphic)
let textSymbol = TextSymbol(color: .blue, size: 12)
let expression = "Test"
let simpleExpression = SimpleLabelExpression(simpleExpression: "Hello")
let labelDefinition = LabelDefinition(labelExpression: simpleExpression, textSymbol: textSymbol)
labelDefinition.placement = .lineCenterAlong
viewModel.lineOverlay.addLabelDefinition(labelDefinition)
}
// MARK: - Body
var body: some View {
NavigationStack {
SceneViewReader { sceneView in
ZStack {
SceneView(scene: scene, camera: $camera, graphicsOverlays: [viewModel.lineOverlay])
.task {
do {
try await loadMobileScenePackage()
} catch {
print(error)
}
}
}
}
}
}
}
Please see the code snippet below. The key here is to set
overlay.labelsAreEnabled = true
so that the overlay allows displaying labels.
import SwiftUI
import ArcGIS
class Model: ObservableObject {
let lineOverlay: GraphicsOverlay = {
let textSymbol = TextSymbol(color: .blue, size: 12)
let expression = SimpleLabelExpression(simpleExpression: "[Name]")
let labelDefinition = LabelDefinition(labelExpression: expression, textSymbol: textSymbol)
labelDefinition.placement = .lineCenterAlong
let overlay = GraphicsOverlay()
overlay.labelsAreEnabled = true
overlay.addLabelDefinition(labelDefinition)
return overlay
}()
}
struct ContentView: View {
@StateObject private var model = Model()
@State private var scene = Scene(basemapStyle: .arcGISLightGray)
@State private var camera: Camera? = Camera(location: Point(x: 113.0, y: 22.0, z: 4_953_394, spatialReference: .wgs84), heading: 360.0, pitch: 0.0, roll: 0)
func addGraphic(latitude: Double, longitude: Double) {
let polyline = Polyline(
points: [
Point(latitude: latitude, longitude: longitude),
Point(x: 115, y: 24)
]
)
let polylineSymbol = SimpleLineSymbol(style: .solid, color: .cyan, width: 2.0)
let polylineGraphic = Graphic(geometry: polyline, attributes: ["Name": "Hello World"], symbol: polylineSymbol)
model.lineOverlay.addGraphic(polylineGraphic)
}
var body: some View {
SceneView(scene: scene, camera: $camera, graphicsOverlays: [model.lineOverlay])
.onAppear {
addGraphic(latitude: 39, longitude: 120)
}
}
}
To add to Michael's comment, if you only want to create a temporary symbol for a graphic, you can also use TextSymbol API. For example, the code below adds a text to the polyline at the middle with some offset.
let lineSymbol = SimpleLineSymbol(style: .solid, color: .red, width: 3)
let textSymbol = TextSymbol(text: "1", color: .yellow, size: 20, horizontalAlignment: .center, verticalAlignment: .middle)
textSymbol.offsetX = 10
let compositeSymbol = CompositeSymbol(symbols: [lineSymbol, textSymbol])
let polyline = Polyline(points: [Point(latitude: 0, longitude: 0), Point(latitude: 10, longitude: 0)])
let graphic = Graphic(geometry: polyline, symbol: compositeSymbol)
However, from a cartography standpoint, it would be better to author the feature layers with labels, or even renderers (that define what symbol should be applied to what features with certain attributes) baked-in, and directly consume it on a mobile device.
I have tried composite symbol before but it is not nicely along the line. I believe Michael's approach is more along the lines of what I wanted. Mind the Pun.