I have a Model class containing the main data (@Published var counter: Int), which is automatically updated periodically by a Timer. I am considering displaying the Model.counter value in a SheetView.
I believe a simple case would look like this.
@main
struct BindingSampleApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
@MainActor
class ContentModel: ObservableObject {
@Published var counter: Int = 0
@Published var isShowingSheet = false
init() {
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
Task { @MainActor in
self.counter += 1
print("counter: \(self.counter)")
}
}
}
func showSheet() {
isShowingSheet = true
}
}
struct ContentView: View {
@StateObject var model = ContentModel()
var body: some View {
VStack {
Button() {
model.showSheet()
} label: {
Text("Show. \(model.counter)")
}
}
.sheet(isPresented: $model.isShowingSheet) {
SheetView(counter: model.counter)
}
.padding()
}
}
struct SheetView: View {
let counter: Int
var body: some View {
VStack {
Text("\(counter)")
.font(.largeTitle)
}
}
}
Due to the increasing complexity of SheetView's logic, I've created a separate SheetModel. What I want to achieve is to display the value of Model.counter, which is updated by a timer, in SheetView by relaying it through SheetModel.
After some trial and error, I have come up with the following two pieces of code that seem to achieve the desired behavior. However, I am not sure if this is the appropriate way to use SwiftUI.
Specifically, for the first code snippet, self.sheetModel = SheetModel(counter: self._counter), I suspect it may not be ideal because it probably causes both Model and SheetModel to reference the same object.
For the second snippet, SheetModel.init(counter: Published.Publisher) { counter.assign(to: &self.$counter) }, it feels a bit verbose.
Is there a simpler and better way to bind Model.counter and SheetModel.counter?
(Tested on Xcode Version 14.3.1 (14E300c), iOS simulator 16.4)
First
@main
struct BindingSampleApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
@MainActor
class ContentModel: ObservableObject {
@Published var counter: Int = 0
@Published var sheetModel: SheetModel?
var isShowingSheet: Bool {
get {
sheetModel != nil
}
set {
if !newValue {
sheetModel = nil
}
}
}
init() {
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
Task { @MainActor in
self.counter += 1
print("counter: \(self.counter)")
}
}
}
func showSheet() {
// I suspect it may not be ideal because it probably causes both Model and
// SheetModel to reference the same object.
self.sheetModel = SheetModel(counter: self._counter)
}
}
@MainActor
class SheetModel: ObservableObject {
@Published var counter: Int = 0
init(counter: Published<Int>) {
print("SheetModel.init")
self._counter = counter
}
}
struct ContentView: View {
@StateObject var model = ContentModel()
var body: some View {
VStack {
Button() {
model.showSheet()
} label: {
Text("Show. \(model.counter)")
}
}
.sheet(isPresented: $model.isShowingSheet) {
SheetView(model: model.sheetModel!)
}
.padding()
}
}
struct SheetView: View {
@ObservedObject var model: SheetModel
var body: some View {
VStack {
Text("\(model.counter)")
.font(.largeTitle)
}
}
}
Second
@MainActor
class ContentModel: ObservableObject {
@Published var counter: Int = 0
@Published var sheetModel: SheetModel?
var isShowingSheet: Bool {
get {
sheetModel != nil
}
set {
if !newValue {
sheetModel = nil
}
}
}
init() {
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
Task { @MainActor in
self.counter += 1
print("counter: \(self.counter)")
}
}
}
func showSheet() {
self.sheetModel = SheetModel(counter: $counter)
}
}
@MainActor
class SheetModel: ObservableObject {
@Published var counter: Int = 0
init(counter: Published<Int>.Publisher) {
print("SheetModel.init")
// it feels a bit verbose.
counter.assign(to: &self.$counter)
}
}
// ContentView and SheetView are same as above.
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
The layout of buttons placed in the bottomBar of the iOS 17β SwiftUI toolbar is different from that in iOS 16 and earlier versions.
When I run the following code in Xcode 15 β3 (15A5195k) and the iOS 17β Simulator, all the buttons on the BottomBar are aligned to the left.
In environments including iOS 17β and earlier versions of iOS 16 with Xcode 14.3.1 (14E300c), only the last button is aligned to the right, while the other buttons are aligned to the left.
Is this a specification change in iOS 17, or is it a bug in iOS 17β?
import SwiftUI
@main
struct ToolBarSampleiOS17BApp: App {
var body: some Scene {
WindowGroup {
NavigationView {
ContentView()
}
.navigationViewStyle(.stack)
}
}
}
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("iOS 17")
}
.toolbar {
ToolbarItemGroup(placement:.bottomBar) {
Button("A") { }
Button("B") { }
Button("C") { }
Button("D") { }
Button("E") { }
}
}
.padding()
}
}
Xcode 15 β3 (15A5195k) and the iOS 17β Simulator
earlier versions of iOS 16
While testing my application on iOS 17 beta, I encountered an issue where the onAppear and onDisappear functions of the View inside the List went into an infinite loop, rendering the application inoperable. This did not occur with iOS 16 or iOS 15.
Is this a bug with the iOS 17 beta? Or did the specifications change with iOS 17?
Here is the reproduction code. On iOS 17 beta, as soon as the application starts, the onAppear and onDisappear of SampleSection go into an infinite loop.
Testing Environment:
Xcode: Version 14.3.1 (14E300c), Version 15.0 beta 3 (15A5195k)
Device: iPhone SE (2nd) iOS 15.2, iPhone SE (3rd) iOS 16.4, iPhone SE(3rd) iOS17 beta 3
import SwiftUI
@main
struct SampleApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
var body: some View {
List {
SampleSection()
section // This issue occurs even without this line.
}
}
private var section: some View {
Section {
ForEach(1...100, id: \.self) { idx in
Text("\(idx)")
}
}
}
}
struct SampleSection: View {
@State private var isLoaded = false
var body: some View {
let _ = Self._printChanges()
Group {
if !isLoaded {
Section("Header") {}
.hidden()
} else {
Section("Header") {
Text("Text")
}
}
}
.onAppear {
NSLog("SampleSection onAppear.")
isLoaded = true
}
.onDisappear() {
NSLog("Sample Section onDisappear.")
isLoaded = false
}
}
}
The following sample code doesn't work as expected. In addition, the behavior differs between iOS 16.0 and iOS 16.1 beta.
If there is a workaround, please let me know.
(I know that navigationDestination(isPresent:destination:) can be substituted in iOS 16.0, but it doesn't work in iOS 16.1 beta)
I reported this issue (FB11599516).
@main
struct StackViewSampleApp: App {
var body: some Scene {
WindowGroup {
NavigationStack {
ContentView()
}
}
}
}
struct ContentView: View {
var body: some View {
VStack {
NavigationLink("SubView") {
SubView()
}
}
.navigationDestination(for: Int.self) { v in
DetailView(value: v)
}
}
}
struct SubView: View {
var body: some View {
NavigationLink(value: 1) {
Text("DetailView")
}
.navigationTitle("SubView")
}
}
struct DetailView: View {
let value: Int
var body: some View {
Text("\(value)")
.navigationTitle("Detail")
}
}
Actual behavior
iOS 16.0 behavior / Xcode Version 14.0 (14A309)
Pressing "SubView" button in RootView, it goes to SubView.
Pressing "DetailView" button in SubView, it goes to DetailView.
Pressing "Back" button, it goes to RootView. (Expect: it goes to SubView)
iOS 16.1 beta(20B5045d) / Xcode Version 14.1 beta 2 (14B5024i)
Pressing "SubView" button in RootView, it goes to SubView.
Pressing "DetailView" button in SubView, it goes to SubView.
Pressing "DetailView" button in SubView, it goes to SubView. (Repeat) (Expect: it goes to DetailView)
Pressing "Back" button in SubView, it goes to DetailView. (Expect: it goes to a previous view)
Hi, I've just finished replacing to NavigationStack, but I'm having trouble with this issue.
The following simple sample code works on iOS 16.0 production, but it doesn't work on iOS 16.1 beta.
When navigationDestination(isPresent:) is called, Xcode displays runtime warning message, but I cannot understand what the means.
A `navigationDestination(isPresented:content:)` is outside an explicit NavigationStack, but inside the detail column of a NavigationSplitView, so it attempts to target the next column. There is no next column after the detail column.
Did you mean to put the navigationDestination inside a NavigationStack?
I didn't use NavigationSplitView and navigationDestination(isPresent:) is inside NavigationStack.
Could someone please point out the problem with this code?
Or is it a SwiftUI bug?
Test environment
Xcode Version 14.1 beta 2 (14B5024i)
iPhone SE (3rd gen) (iOS 16.1)
Sample Code
@main
struct StackViewSampleApp: App {
var body: some Scene {
WindowGroup {
NavigationStack {
ContentView()
}
}
}
}
struct ContentView: View {
var body: some View {
List {
NavigationLink("SubView1") {
SubView1()
}
NavigationLink("SubView2") {
SubView2()
}
}
}
}
struct SubView1: View {
@State private var isPresent: Bool = false
var body: some View {
Button("A") {
isPresent = true
}
.navigationDestination(isPresented: $isPresent) {
Text("TEST")
}
}
}
struct SubView2: View {
enum ViewType {
case a, b, c
}
@State private var selection: ViewType? = nil
let viewTypes: [ViewType] = [.a, .b, .c]
var body: some View {
List(selection: $selection) {
ForEach(viewTypes, id: \.self) { t in
NavigationLink(value: t) {
Text("\(String(describing: t))")
}
}
}
.navigationDestination(isPresented: .init(get: {
selection != nil
}, set: { newValue in
if !newValue {
selection = nil
}
})) {
Text("\(String(describing: selection))")
}
}
}