I would like to subscribe to UIPasteboard changes in SwiftUI with onReceive.
pHasStringsPublisher will not be updated as soon as something in the clipboard changes and I don't understand why.
import SwiftUI
struct ContentView: View {
let pasteboard = UIPasteboard.general
#State var pString: String = "pString"
#State var pHasStrings: Bool = false
#State var pHasStringsPublisher: Bool = false
var body: some View {
VStack{
Spacer()
Text("b: '\(self.pString)'")
.font(.headline)
Text("b: '\(self.pHasStrings.description)'")
.font(.headline)
Text("p: '\(self.pHasStringsPublisher.description)'")
.font(.headline)
Spacer()
Button(action: {
self.pString = self.pasteboard.string ?? "nil"
self.pHasStrings = self.pasteboard.hasStrings
}, label: {
Text("read pb")
.font(.largeTitle)
})
Button(action: {
self.pasteboard.items = []
}, label: {
Text("clear pb")
.font(.largeTitle)
})
Button(action: {
self.pasteboard.string = Date().description
}, label: {
Text("set pb")
.font(.largeTitle)
})
}
.onReceive(self.pasteboard
.publisher(for: \.hasStrings)
.print()
.receive(on: RunLoop.main)
.eraseToAnyPublisher()
, perform:
{ hasStrings in
print("pasteboard publisher")
self.pHasStringsPublisher = hasStrings
})
}
}
As far as I know, none of UIPasteboard's properties are documented to support Key-Value Observing (KVO), so publisher(for: \.hasStrings) may not ever publish anything.
Instead, you can listen for UIPasteboard.changedNotification from the default NotificationCenter. But if you are expecting the user to copy in a string from another application, that is still not sufficient, because a pasteboard doesn't post changedNotification if its content was changed while your app was in the background. So you also need to listen for UIApplication.didBecomeActiveNotification.
Let's wrap it all up in an extension on UIPasteboard:
extension UIPasteboard {
var hasStringsPublisher: AnyPublisher<Bool, Never> {
return Just(hasStrings)
.merge(
with: NotificationCenter.default
.publisher(for: UIPasteboard.changedNotification, object: self)
.map { _ in self.hasStrings })
.merge(
with: NotificationCenter.default
.publisher(for: UIApplication.didBecomeActiveNotification, object: nil)
.map { _ in self.hasStrings })
.eraseToAnyPublisher()
}
}
And use it like this:
var body: some View {
VStack {
blah blah blah
}
.onReceive(UIPasteboard.general.hasStringsPublisher) { hasStrings = $0 }
}
Related
I'm new to Swift / Firebase and need some help with my code.
I have a simple create new account page, and I am using Firebase.
I want to create a user when a button is tapped - change button colour/text to reflect this - then return to HomeView
Now, I want to include all the Auth.auth() in a separate function on a separate swift file - to keep my View code clean.
Currently, I'm setting the isShowingNewAccountView = false to return back to HomeView (via a NavigationLink on HomeView) - isShowingAccountView is a Binding variable from HomeView.
If I put all the Auth.auth() code into a separate function, is it possible to change the value of the isShowingAccountView to 'false' when a user is created from within the function? Is there a more elegant alternative
Button(action: {
Auth.auth().createUser(withEmail: newUser.userEmail, password: newUser.userPassword) { authDataResult, error in
if error != nil {
print("Error detected")
print(error!.localizedDescription)
isShowingNewAccountView = true //Binding variable from HomeView - Keep showing NewAccountView
newUser.successNewAccountCreated = false
return
}
else
{
print("Account Successfully Created")
isShowingNewAccountView = false //Binding variable from HomeView - Revert back to HomeView
newUser.successNewAccountCreated = true
return
}
}
}, label: {
CustomButton(buttonText: (!newUser.successNewAccountCreated ? "Create New Account" : "New Account Created"), colourVar: newUser.successNewAccountCreated ? .green : (isInputAppropriate() ? .accentColor : .gray)
})
On HomeView I have:
NavigationLink(destination: NewAccountView(isShowingNewAccountView: $isShowingNewAccountView), isActive: $isShowingNewAccountView) {
//passing isShowingAccountView as a binding $
EmptyView()
}
Thanks in advance.
Maybe this approach may help you:
Three views (just as an example):
ContentView: It shows either Page1 or Page2, depending on the var isShowingNewAccountView.
Page1: It is the page you use to create a new user, it will be shown when isShowingNewAccountView is false. It calls the function signUp from the AuthenticationViewModel.
Page2: It is the page that will be opened when a new User has been created and isShowingNewAccountView is true
One ViewModel (also just as an example)
The authentication is handled in the AuthenticationViewModel. It is handling the functions to signUp, signIn and signOut. It also holds isShowingNewAccountView.
Views
import SwiftUI
import FirebaseFirestore
import Firebase
import FirebaseFirestoreSwift
struct Page1: View {
#EnvironmentObject var authenticationViewModel: AuthenticationViewModel
#State private var buttonPressed = false
#State private var email: String = "sebastian#hello.me"
#State private var password: String = "password"
var body: some View {
VStack(){
Text("Register")
.font(Font.system(size: 24, weight: .bold))
.padding()
.padding(.vertical, 50)
TextField("E-Mail", text: $email)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
SecureField("Password", text: $password)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
Button(action: {
buttonPressed.toggle()
authenticationViewModel.signUp(email: email, password: password){ success, uid in
if success {
print("UID: \(uid)")
} else {
print("Uh-oh")
}
buttonPressed = false
}
}) {
Text(buttonPressed ? "In Progress" : "Sign Up - Create User")
.foregroundColor(buttonPressed ? Color.pink : Color.cyan)
}
}
}
}
struct Page2: View {
#EnvironmentObject var authenticationViewModel: AuthenticationViewModel
var body: some View {
Button(action: {
authenticationViewModel.isShowingNewAccountView.toggle()
}) {
Text("Back to other page")
}
}
}
struct ContentView: View {
#EnvironmentObject var authenticationViewModel: AuthenticationViewModel
var body: some View {
if authenticationViewModel.isShowingNewAccountView {
Page2()
} else {
Page1()
}
}
}
ViewModel for Authentication
import Foundation
import FirebaseFirestore
import Firebase
import FirebaseFirestoreSwift
class AuthenticationViewModel: ObservableObject {
let db = Firestore.firestore()
#Published var isShowingNewAccountView: Bool = false
// Sign Up
func signUp(email: String, password: String, completion: #escaping (Bool, String)->Void) {
Auth.auth().createUser(withEmail: email, password: password) { authResult, error in
// ERROR AND SUCCESS HANDLING
if error != nil {
// ERROR HANDLING
print(error?.localizedDescription as Any)
completion(false, "ERROR")
} else {
// SUCCESS HANDLING
self.isShowingNewAccountView = true
completion(true, authResult?.user.uid ?? "")
}
}
}
// Sign In
func signIn(email: String, password: String, completion: #escaping (Bool, String)->Void) {
Auth.auth().signIn(withEmail: email, password: password) { (authResult, error) in
// ERROR AND SUCCESS HANDLING
if error != nil {
// ERROR HANDLING
print(error?.localizedDescription as Any)
completion(false, "ERROR")
}
completion(true, authResult?.user.uid ?? "")
}
}
// Sign Out
func signOut(_ completion: #escaping (Bool) ->Void) {
try! Auth.auth().signOut()
completion(true)
}
// Create new user
func createNewUser(name: String, id: String) {
do {
let newUser = User(name: name, id: id)
try db.collection("users").document(newUser.id!).setData(from: newUser) { _ in
print("User \(name) created")
}
} catch let error {
print("Error writing user to Firestore: \(error)")
}
}
}
Alternative with a NavigationLink
In your approach you are using a NavigationLink. That would work as well. In that case the ContentView as well as the Page1 view are slightly different (Page2 stays as it is):
ContentView Update
In the ContentView it is not necessary do make the decision which view will be shown.
struct ContentView: View {
#EnvironmentObject var authenticationViewModel: AuthenticationViewModel
var body: some View {
Page1()
}
}
Page1 Update
On Page1 we now need the NavigationView and the NavigationLink. As soon as isShowingNewAccountView becomes true, it opens Page2, but for me, the NavigationLink is something you can press, like a button. But anyway:
struct Page1: View {
#EnvironmentObject var authenticationViewModel: AuthenticationViewModel
#State private var buttonPressed = false
#State private var email: String = "sebastian#hello.me"
#State private var password: String = "password"
var body: some View {
NavigationView(){
VStack(){
Text("Register")
.font(Font.system(size: 24, weight: .bold))
.padding()
.padding(.vertical, 50)
TextField("E-Mail", text: $email)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
SecureField("Password", text: $password)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
Button(action: {
buttonPressed.toggle()
authenticationViewModel.signUp(email: email, password: password){ success, uid in
if success {
print("UID: \(uid)")
} else {
print("Uh-oh")
}
buttonPressed = false
}
}) {
Text(buttonPressed ? "In Progress" : "Sign Up - Create User")
.foregroundColor(buttonPressed ? Color.pink : Color.cyan)
}
NavigationLink(destination: Page2(), isActive: $authenticationViewModel.isShowingNewAccountView){
EmptyView()}
}
}
}
}
Please see the gif:
Im learning swift and this error/warning is driving me crazy because I cant see what call Im making that causing it... The Xcode warning only shows up in my #main struct
Modifying state during view update, this will cause undefined behavior.
I thought it might be in the ListView, but I realized the warning only shows after the "Submit Post" button is it.
Im looking for a fix, but more importantly and explanation as to why this is happening and the proper usage moving forward.
import SwiftUI
import Firebase
#main
struct SocialcademyApp: App {
init() {
FirebaseApp.configure()
}
var body: some Scene {
WindowGroup {
PostsList()
}
}
}
struct PostsList: View {
#StateObject var viewModel = PostsViewModel()
#State private var searchText = ""
#State private var showNewPostForm = false
var body: some View {
NavigationView {
List(viewModel.posts) { post in
if searchText.isEmpty || post.contains(searchText) {
PostRow(post: post)
}
}
.searchable(text: $searchText)
.navigationTitle("Posts")
.toolbar {
Button {
showNewPostForm = true
} label: {
Label("New Post", systemImage: "square.and.pencil")
}
}
.sheet(isPresented: $showNewPostForm) {
NewPostView(creationAction: viewModel.makeCreationAction())
}
}
}
}
struct NewPostView: View {
typealias CreationAction = (Post) async throws -> Void
let creationAction: CreationAction
#State private var post = Post(title: "", content: "", authorName: "")
#State private var state = FormState.idle
#Environment(\.dismiss) private var dismiss
var body: some View {
NavigationView {
Form {
Section {
TextField("Title", text: $post.title)
TextField("Author Name", text: $post.authorName)
}
Section {
TextField("Content", text: $post.content)
.multilineTextAlignment(.leading)
}
Button(action: createPost, label: {
if state == .working {
ProgressView() } else {
Text("Submit Post")
}
})
.font(.headline)
.frame(maxWidth: .infinity)
.foregroundColor(.white)
.padding()
.listRowBackground(Color.accentColor)
}
}
.navigationTitle("New Post")
.disabled(state == .working)
.alert("Cannot Create Post", isPresented: $state.isError, actions: {}) {
Text("Sorry, something went wrong")
}
.onSubmit {
createPost()
}
}
private func createPost() {
print("[NewPostForm] creating a new post")
Task {
state = .working
do {
try await creationAction(post)
dismiss()
} catch {
state = .error
print("[NewPostForm] Cannot create post: \(error)")
}
}
}
}
private extension NewPostView {
enum FormState {
case idle, working, error
var isError: Bool {
get {
self == .error
}
set {
guard !newValue else { return }
self = .idle
}
}
}
}
#MainActor
class PostsViewModel: ObservableObject {
#Published var posts = [Post.testPost]
func makeCreationAction() -> NewPostView.CreationAction {
return { [weak self] post in
try await PostsRepository.create(post)
self?.posts.insert(post, at: 0)
}
}
}
I'm trying to present two sheets in SwiftUI. The first sheet (SecondScreen) opens up on the Main Page (tapping the Navigation Tool Bar Icon) and the second sheet is a ShareSheet which should pop up inside the SecondScreen as an option. I have used a Form to build the SecondScreen. In the Simulator and on my device, the ShareSheet doesn't appear. I hope this is just a bug and not something Apple doesn't allow without big UI changes.
I tried to open the ShareSheet, while having the SecondScreen as a .fullScreenCover., instead of .sheet but the button still doesn't react.
Example
import SwiftUI
struct ContentView: View {
#State var showMore: Bool = false
var body: some View {
NavigationView {
Text("Main Page")
.padding()
.navigationBarTitle("Main Page")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {
showMore.toggle()
}, label: {
Image(systemName: "ellipsis.circle")
})
.sheet(isPresented: $showMore, content: {
SecondScreen()
})
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct SecondScreen: View {
var body: some View {
NavigationView {
Form {
Section {
Button(action: {
ShareID (Info: "https://www.google.com")}, label: { Text("Share")
})
}
}
}
}
}
}
func ShareID(Info: String){
let infoU = Info
let av = UIActivityViewController(activityItems: [infoU], applicationActivities: nil)
UIApplication.shared.windows.first?
.rootViewController?.present(av, animated: true,
completion: nil)
}
Thank you!
this is another approach to popup your sheets, even works on my mac:
import SwiftUI
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
#State var showMore: Bool = false
var body: some View {
NavigationView {
Text("Main Page")
.padding()
.navigationBarTitle("Main Page")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: { showMore.toggle() }) {
Image(systemName: "ellipsis.circle")
}
.sheet(isPresented: $showMore) {
SecondScreen()
}
}
}
}
}
}
struct SecondScreen: View {
#State var shareIt = false
#State var info = "https://www.google.com"
var body: some View {
Button(action: {shareIt = true}) {
Text("Share")
}
.sheet(isPresented: $shareIt, onDismiss: {shareIt = false}) {
ShareSheet(activityItems: [info as Any])
}
}
}
struct ShareSheet: UIViewControllerRepresentable {
typealias Callback = (_ activityType: UIActivity.ActivityType?, _ completed: Bool, _ returnedItems: [Any]?, _ error: Error?) -> Void
let activityItems: [Any]
let applicationActivities: [UIActivity]? = nil
let excludedActivityTypes: [UIActivity.ActivityType]? = nil
let callback: Callback? = nil
func makeUIViewController(context: Context) -> UIActivityViewController {
let controller = UIActivityViewController(
activityItems: activityItems,
applicationActivities: applicationActivities)
controller.excludedActivityTypes = excludedActivityTypes
controller.completionWithItemsHandler = callback
return controller
}
func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) { }
}
I use SwiftUI for my Mac OS app. When I change selection (from Inbox to Today) of the List in SidebarView with mouse click didSet is called twice(Sidebar body is calculated twice too). If I change selection with arrow (down | up) didSet is called once. Is it bug? I am sorry for my english.
DoubleColumn App
Logs:
with mouse click:
willSet = Optional(MiniTodo.NavigationItem.inbox) 2021-04-18 00:36:20 +0000
didSet = Optional(MiniTodo.NavigationItem.inbox) 2021-04-18 00:36:20 +0000
willSet = Optional(MiniTodo.NavigationItem.inbox) 2021-04-18 00:36:20 +0000
didSet = Optional(MiniTodo.NavigationItem.today) 2021-04-18 00:36:20 +0000
with arrow:
willSet = Optional(MiniTodo.NavigationItem.inbox) 2021-04-18 00:37:11 +0000
didSet = Optional(MiniTodo.NavigationItem.today) 2021-04-18 00:37:11 +0000
import SwiftUI
class UIState: ObservableObject {
#Published var navigationItem: NavigationItem? = .inbox {
didSet {
print("didSet = \(navigationItem) \(Date())")
}
willSet {
print("willSet = \(navigationItem) \(Date())")
}
}
}
struct MiniTodoApp: App {
let persistenceController = PersistenceController.shared
#StateObject var uiState: UIState = UIState()
var body: some Scene {
WindowGroup {
NavigationView {
SidebarView()
.environmentObject(uiState)
//ContentView()
}
.navigationViewStyle(DoubleColumnNavigationViewStyle())
.environment(\.managedObjectContext, persistenceController.container.viewContext)
}
}
}
struct SidebarView: View {
#Environment(\.managedObjectContext) private var viewContext
#EnvironmentObject var uiState: UIState
#FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Folder.name, ascending: true)],
animation: .default)
private var folders: FetchedResults<Folder>
var body: some View {
print("==== Sidebar View ====")
print(#function)
print("======================")
print("")
return List(selection: $uiState.navigationItem) {
Group {
Label("Inbox", systemImage: "tray")
.tag(NavigationItem.inbox)
Label("Today", systemImage: "sun.max.fill")
.listItemTint(.yellow)
.tag(NavigationItem.today)
Label("Plan", systemImage: "calendar")
.listItemTint(.red)
.tag(NavigationItem.plan)
Label("Completed", systemImage: "checkmark.square.fill")
.listItemTint(.green)
.tag(NavigationItem.completed)
}
Section(header:
HStack {
Text("Lists")
Spacer()
//if self.onHover {
Button {
withAnimation {
newFolder()
}
} label: {
Image(systemName: "plus.circle")
}
.buttonStyle(BorderlessButtonStyle())
.padding(EdgeInsets(top: 0.0, leading: 0.0, bottom: 0.0, trailing: 10.0))
// }
}
) {
ForEach(folders) { folder in
FolderRow(folder: folder)
.tag(NavigationItem.folder(folder: folder))
}
} // Section
.collapsible(false)
} // List
.listStyle(SidebarListStyle())
.toolbar {
ToolbarItem(placement: .navigation){
Button(action: toggleSidebar, label: {
Image(systemName: "sidebar.left")
})
}
}
} // body
// Toggle Sidebar Function
func toggleSidebar() {
NSApp.keyWindow?.firstResponder?.tryToPerform(#selector(NSSplitViewController.toggleSidebar(_:)), with: nil)
}
func newFolder() {
withAnimation {
let newItem = Folder(context: viewContext)
newItem.name = "It is new folder"
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
}
You can use the debounce function on top of the navigationItem publisher and you will only receive the didSet once:
class UIState: ObservableObject {
#Published var navigationItem: NavigationItem? = .inbox
var cancellable: AnyCancellable?
init() {
self.cancellable = $navigationItem.debounce(
for: .microseconds(1), scheduler: RunLoop.main
).sink { _ in
print("DID SET")
// Do Whatever you want
}
}
}
I just want to do some test like this ↓
Create one publisher from first view
Pass it to second view
Bind the publisher with some property in second view and try to show it on screen
The code is ↓ (First View)
struct ContentView: View {
let publisher = URLSession(configuration: URLSessionConfiguration.default)
.dataTaskPublisher(for: URLRequest(url: URL(string: "https://v.juhe.cn/joke/content/list.php?sort=asc&page=&pagesize=&time=1418816972&key=aa73ebdd8672a2b9adc9dbb2923184c8")!))
.map(\.data.description)
.replaceError(with: "Error!")
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
var body: some View {
NavigationView {
List {
NavigationLink(destination: ResponseView(publisher: publisher)) {
Text("Hello, World!")
}
}.navigationBarTitle("Title", displayMode: .inline)
}
}
}
(Second View)
struct ResponseView: View {
let publisher: AnyPublisher<String, Never>
#State var content: String = ""
var body: some View {
HStack {
VStack {
Text(content)
.font(.system(size: 12))
.onAppear { _ = self.publisher.assign(to: \.content, on: self) }
Spacer()
}
Spacer()
}
}
}
But the code is not working. The request failed with message blow ↓
2020-11-11 11:08:04.657375+0800 PandaServiceDemo[83721:1275181] Task <6B53516E-5127-4C5E-AD5F-893F1AEE77E8>.<1> finished with error [-999] Error Domain=NSURLErrorDomain Code=-999 "cancelled" UserInfo={NSErrorFailingURLStringKey=https://v.juhe.cn/joke/content/list.php?sort=asc&page=&pagesize=&time=1418816972&key=aa73ebdd8672a2b9adc9dbb2923184c8, NSLocalizedDescription=cancelled, NSErrorFailingURLKey=https://v.juhe.cn/joke/content/list.php?sort=asc&page=&pagesize=&time=1418816972&key=aa73ebdd8672a2b9adc9dbb2923184c8}
Can someone tell me what happened and what is the right approach to do this?
The issue here is, the Subscription isn't stored anywhere. You have to store it in a AnyCancellable var and retain the subscription.
Use .print() operator whenever you are debugging combine related issues. I find it really useful.
The right approach is to extract the publisher and subscription into an ObservableObject and inject it into the View or use #StateObject
class DataProvider: ObservableObject {
#Published var content: String = ""
private var bag = Set<AnyCancellable>()
private let publisher: AnyPublisher<String, Never>
init() {
publisher = URLSession(configuration: URLSessionConfiguration.default)
.dataTaskPublisher(for: URLRequest(url: URL(string: "https://v.juhe.cn/joke/content/list.php?sort=asc&page=&pagesize=&time=1418816972&key=aa73ebdd8672a2b9adc9dbb2923184c8")!))
.map(\.data.description)
.print()
.replaceError(with: "Error!")
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
func loadData() {
publisher.assign(to: \.content, on: self).store(in: &bag)
}
}
struct ContentView: View {
#StateObject var dataProvider = DataProvider()
var body: some View {
NavigationView {
List {
NavigationLink(destination: ResponseView(dataProvider: dataProvider)) {
Text("Hello, World!")
}
}.navigationBarTitle("Title", displayMode: .inline)
}
}
}
struct ResponseView: View {
let dataProvider: DataProvider
var body: some View {
HStack {
VStack {
Text(dataProvider.content)
.font(.system(size: 12))
.onAppear {
self.dataProvider.loadData()
}
Spacer()
}
Spacer()
}
}
}
Please note that we have used #StateObject to make sure that DataProvider instance does not get destroyed when the view updates.
You need to store the subscription, otherwise it would be de-initialized and automatically cancelled.
Typically, this is done like this:
var cancellables: Set<AnyCancellable> = []
// ...
publisher
.sink {...}
.store(in: &cancellables)
So, you can create a #State property like the above, or you can use .onReceive:
let publisher: AnyPublisher<String, Never>
var body: some View {
HStack {
// ...
}
.onReceive(publisher) {
content = $0
}
}
You should be careful with the above approaches, since if ResponseView is ever re-initialized, it would get a copy of the publisher (most publishers are value-types), so it would start a new request.
To avoid that, add .share() to the publisher:
let publisher = URLSession(configuration: URLSessionConfiguration.default)
.dataTaskPublisher(...)
//...
.share()
.eraseToAnyPublisher()
In terms of SwiftUI, you are doing something fundamentally wrong: creating the publisher from the View. This means a new publisher will be created every time ContentView is instantiated, and for all means and purposes this can happen a lot of times, SwiftUI makes no guarantees a View will be instantiated only once.
What you need to do is to extract the published into some object, which is either injected from upstream, or managed by SwiftUI, via #StateObject.
Well there is 2 way for this Job: way one is better
Way 1:
import SwiftUI
struct ContentView: View {
#State var urlForPublicsh: URL?
var body: some View {
VStack
{
Text(urlForPublicsh?.absoluteString ?? "nil")
.padding()
Button("Change the Publisher") {urlForPublicsh = URL(string: "https://stackoverflow.com")}
.padding()
SecondView(urlForPublicsh: $urlForPublicsh)
}
.onAppear()
{
urlForPublicsh = URL(string: "https://www.apple.com")
}
}
}
struct SecondView: View {
#Binding var urlForPublicsh: URL?
var body: some View {
Text(urlForPublicsh?.absoluteString ?? "nil")
.padding()
}
}
Way 2:
import SwiftUI
class UrlForPublicshModel: ObservableObject
{
static let shared = UrlForPublicshModel()
#Published var url: URL?
}
struct ContentView: View {
#StateObject var urlForPublicshModel = UrlForPublicshModel.shared
var body: some View {
VStack
{
Text(urlForPublicshModel.url?.absoluteString ?? "nil")
.padding()
Button("Change the Publisher") {urlForPublicshModel.url = URL(string: "https://stackoverflow.com")}
.padding()
SecondView()
}
.onAppear()
{
urlForPublicshModel.url = URL(string: "https://www.apple.com")
}
}
}
struct SecondView: View {
#ObservedObject var urlForPublicshModel = UrlForPublicshModel.shared
var body: some View {
Text(urlForPublicshModel.url?.absoluteString ?? "nil")
.padding()
}
}