I am trying to achieve a naturally feeling movement of an entity, but the drag gesture always gives me movement perpendicular the view. That means if I restrict y to the stay the same I can only move the model left and right. A movement (of the mouse) upwards would result exclusive into a movement up/down.
My model has many sub entities and I found that I need to create a box as collision shape so that I can move the entire model. Previously if I generated collision shapes it would only move the individual part.
This code is not working mostly, except the restriction for y which feels very much like a hack.
Is there a facility by the system that would rotate the movement vector along the x axis such that y becomes zero?
Or am I approaching this wrong? how would you move objects in immersive space usually?
import SwiftUI
import RealityKit
import RealityKitContent
struct ImmersiveView: View {
@State private var initialDragOffset: SIMD3<Float> = .zero
var body: some View {
RealityView { content in
async let model = Entity(named: "01-TFKmono2-Normal-PremiumGrey", in: realityKitContentBundle)
if let model = try? await model
{
// Set the model's position to the world origin
model.position = [0, 0, 0]
// Offset the model in the y direction by half its height
let boundingBox = model.visualBounds(relativeTo: nil)
let boxSize = boundingBox.extents
// Create the collision box and set it to the model
let collision = CollisionComponent(shapes: [.generateBox(size: [boxSize.x, boxSize.y, boxSize.z]).offsetBy(translation: .init(x: 0, y: boxSize.y / 2, z: 0))], isStatic: false)
model.components.set(collision)
// Set the InputTargetComponent and add the model to the content
model.components.set(InputTargetComponent())
content.add(model)
// move the mode 2 meters in front of viewer
model.position.z = -2
}
}
.gesture(DragGesture()
.targetedToAnyEntity()
.onChanged { value in
if initialDragOffset == .zero {
// Calculate the initial drag offset at the beginning of the drag
let startLocation = value.convert(value.startLocation3D, from: .local, to: value.entity.parent!)
initialDragOffset = value.entity.position - SIMD3<Float>(startLocation.x, startLocation.y, startLocation.z)
}
// Convert the 3D location of the drag gesture to the entity's parent coordinate space
let dragLocation = value.convert(value.location3D, from: .local, to: value.entity.parent!)
var newPosition = SIMD3<Float>(dragLocation.x, dragLocation.y, dragLocation.z) + initialDragOffset
// don't modify y coordinate so that model remains standing on ground
newPosition.y = value.entity.position.y
// Set the entity's position to the new position
value.entity.position = newPosition
}.onEnded({ value in
initialDragOffset = .zero
}))
}
}
#Preview {
ImmersiveView()
.previewLayout(.sizeThatFits)
}
Post
Replies
Boosts
Views
Activity
Do you always have to have a main window? Or is there a way to only have a mixed experience?
how would you go about selecting a model from a list and once you select it, I want to hide the list and instead show the model in front of you in an immersive view. Is there a way to re-show the list?
I know that there's the X button at the bottom of the main window, but once you close that, how do you get back to it?
Do watchOS Complications support interactivity via App Intents?
I see that the Button initializer IS supported on watchOS, but any widget I try this on only seems to open the watch app.
I found that can customise the range of the x and y axes. But my problem is that for line marks some data might just be outside the axes range. If I add .clipped to the chart that would limit it, but then it also clips off some of the axis marks.
No Clipping:
With Clipping: you see the green line go over the AxisValueLabel area and the topmost label is clipped off.
One way I can imagine addressing this without enabling clipping is to create an interpolated value for values that are just outside the chart range so that the line ends exactly at the min/max.
Is there a way to have a point mark only come out as a very small circle? I want PointMarks that are basically just 1 point large black dots.
I want to draw a chart where I have two line marks (one for each child each) and behind it area marks for percentiles. If I don't add any foregroundStyle then the line marks get connected.
struct GrowthChart: View
{
@ChartContentBuilder
func oneSlice(row: PercentileRow, slice: Slice) -> some ChartContent
{
AreaMark(
x: .value("Day", row.month),
yStart: .value("Max", row.dictionary[slice.startKey]!),
yEnd: .value("Min", row.dictionary[slice.endKey]!)
)
.foregroundStyle(by: .value("Percentile", slice.number))
}
let percentileColors: [Color] = [
.blue.opacity(0.1),
.blue.opacity(0.2),
.blue.opacity(0.3),
.blue.opacity(0.4),
.blue.opacity(0.5),
.blue.opacity(0.4),
.blue.opacity(0.3),
.blue.opacity(0.2),
.blue.opacity(0.1)
]
var body: some View
{
VStack {
Chart {
ForEach(percentiles.rows) { row in
ForEach(slices) { slice in
oneSlice(row: row, slice: slice)
}
}
ForEach(children) { child in
ForEach(child.data) { dataPoint in
LineMark(
x: .value("Month", dataPoint.month),
y: .value("Height", dataPoint.height)
)
.foregroundStyle(by: .value("Name", child.name))
}
}
}
.chartForegroundStyleScale(
range: Gradient (colors: percentileColors)
)
.aspectRatio(contentMode: .fit)
.padding()
Spacer()
}.padding()
}
}
I don't understand how to group the individual marks. Apparently you have to add a foregroundStyle to each with a .by specifying a category. I found this chartForegroundStyleScale in an example on the internet, but I can only get it to show this gradient. But how do I specify different colors for the LineMarks?
iOS 14 introduces some laziness in certain views, like for example there are now lazy variants of HStack and VStack. I’ve been working on a SwiftUI-version of an app of mine and the findings of my research for these bugs led me to believe that Apple added some lazy optimisations to Lists as well.
Unfortunately those optimisations seem to be breaking selection and presentation of detail views to a degree that they would be show stoppers for any universal app relying on List and its automatic promotion to being a split view on iPad.
I filed this as FB8521674 in the Apple Feedback App. I am posting the full report here as well as these findings might be helpful to other developers who were as stuck as I am, hoping that Apple will fix this issue .
Bug Report
On iPad, List is supposed to present the destination of NavigationLinks when the link’s tag matches the selection binding’s value. But that only occurs if the referenced row is presently visible.
Setting selection programmatically only presents the detail view if the referenced row is presently visible. That goes both for setting the selected binding to a value before the list appears as well as setting it while it is visible.
The selection highlight is not being updated by programmatic selection. Also, if you hide the list on iPad via the button on the top left and show it again, the selection highlight also disappears.
Steps to Reproduce
Open the attached sample project
Run it in iPad simulator in Landscape orientation
Notice that it shows “Africa/Asmara Detail” on the right side, but there is no selection highlight on the list on the left
If you click any row, then selection highlight is updated, as is the detail view
While at the top of the list, press the arrow button above the list
Notice that nothing changes
Now scroll down the list. As soon as the row for “America/Adak” appears, the detail view is being updated, but the selection highlight is still missing
Rotate to Portrait orientation (which hides the List as soon as a selection is being made)
Scroll to the top of the list
Tap the arrow button again, nothing happens
Scroll down until “America/Adak” becomes visible, now the detail view gets updated (selection highlight does not show) and the list disappears
What I expected to happen
when the app shows in landscape (#3) the “Africa/Asmera” row should be highlighted
when you tap the arrow (#5) the “America/Adak” row should be highlighted (and scrolled to) and the related detail view be presented
when in Portrait, the sudden dismissal of the list is confusing. I would prefer to have the auto-hiding only occur if the user makes a change of the selection. But if you must hide it, then at least the selection highlight should be visible
hiding and re-showing the list on iPad Landscape should preserve the selection highlight
What actually happened
programmatic change of the selection never updates the highlight
a detail page is only presented as soon as the referenced row becomes visible
if you are scrolling, on iPad Portrait, and a selected item becomes visible the list is suddenly dismissed
hiding and re-showing the list (iPad Landscape) doesn’t preserve selection highlight
Sample Project
The sample project is so short that I can reproduce it here for you:
import SwiftUI
@main
struct SelectionBugApp: App {
// the navigation presentation is only done on the
// initially visible rows
// selection is never updated
@State var selectedID: String? = "Africa/Asmara"
	var body: some Scene {
		WindowGroup {
			NavigationView {
				List(selection: $selectedID) {
					ForEach(TimeZone.knownTimeZoneIdentifiers,
									id: \.self) { identifier in
						NavigationLink(identifier,
						 destination: Text("\(identifier) Detail"),
						 tag: identifier,
						 selection: $selectedID)
						}
					}
					.navigationTitle("Time Zones")
					.navigationBarItems(trailing:
						 Button(action: {
							 // this presents the navigation destination,
							 // but only if the row has been visible
							 // selection is never updated
							 self.selectedID = "America/Adak"
						 }, label: {
							 Image(systemName: "location.fill")
						 })
					)
				 }
	 }
	}
}
Just post it into a new iOS SwiftUI app and you can follow along.
I have the following code working to scroll to a given item. But the combination of ScrollView + LazyStackview doesn't give me proper selection highlighting, like a List would. So my questions are:
is there a way to scroll a List to a specific item, i.e. can I use ScrollViewReader with Lists?
is there a way to emulate the row highlighting which you press on the cell? And to abort selection in the default way by scrolling a bit?
struct ContentView: View {
let timezones = TimeZone.knownTimeZoneIdentifiers.map { TimeZone(identifier: $0)! }
var body: some View {
let continents = Set(timezones.compactMap { $0.continent }).sorted()
ScrollView {
ScrollViewReader { value in
LazyVStack(alignment: .leading, spacing: 5, pinnedViews: [.sectionHeaders]) {
ForEach(continents, id: \.self) { continent in
let continentTZ = timezones.filter { $0.continent == continent}
Section(header: HeaderView(title: continent)) {
ForEach(continentTZ) { timezone in
Button(action: {
// this will dismiss the selection
}) {
VStack {
TimeZoneCell(timezone: timezone)
Divider()
}
}
}
}
}
}.onAppear() {
value.scrollTo(TimeZone.current.identifier, anchor: .center)
}
}
}.navigationBarTitle(Text("Time Zones"), displayMode: .inline)
}
}
Wow, this prowess on the cube at the start. Had me skip back to the start a couple of times. I must ask ... is that a cut right at the end hiding a few more moves? Or was Lui done exactly when finishing his introductory remarks?
cheers
Oliver
I had some issues with the code, so I ended up creating an initializer instead of relying on automatic-synthesis. Also, I don't quite understand why all functions needed to be public... possibly because only public functions can be called from other pages?
{
public let instrument: Instrument.Kind
public let length: Int
public let notes: [MIDINotes]
public init(instrument: Instrument.Kind, length: Int, notes: [MIDINotes])
{
self.instrument = instrument
self.length = length
self.notes = notes
}
public func note(for frame: Int) -> MIDINotes {
guard frame < notes.count else {
return .rest
}
return notes[frame]
}
}
Here's my solution for the performance:
func performance(owner: Assessable) {
let numberOfBeats = 32 // two bars of 4/4
let duration = 16.0 // seconds
var bass = Track(instrument: .bassGuitar, length: numberOfBeats, notes: bassNotes)
var piano = Track(instrument: .piano, length: numberOfBeats, notes: trebleNotes)
let tracks = [bass, piano]
let interval = duration / Double(numberOfBeats)
var index = 0
Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { (timer) in
guard index < numberOfBeats else
{
owner.endPerformance()
timer.invalidate()
return
}
for track in tracks {
playInstrument(track.instrument, note: track.note(for: index))
}
index += 1
}
}
Hi,I have a USDZ model loaded and as far as I can tell I am using RealityKit to display it. Is there a way to change the properties of certain material so that the already-displaying model gets a different color in parts where this material is used?i.e. I have a model of a baby stroller and I want to switch between a couple of different colors for the sun roof.I couldn't find any documentation or sample to that effect.Any help would be much appreciatedOliver