I guess I've solved my question as follows.
import SwiftUI
struct ContentView: View {
@ObservedObject var monster: MonsterObservable
var body: some View {
GeometryReader { geo in
ZStack {
HStack(spacing: 0.0) {
LeftView()
.frame(width: geo.size.width / 2.0, height: geo.size.height, alignment: .leading)
RightView(showMe: $monster.showDialog)
.frame(width: geo.size.width / 2.0, height: geo.size.height, alignment: .trailing)
}
ShowDialogView(isShowing: monster.showDialog) {
}
.frame(width: 500, height: 600, alignment: .center)
.cornerRadius(10.0)
}
}
}
}
class MonsterObservable: ObservableObject {
@Published var showDialog = false
}
import SwiftUI
struct RightView: View {
@Binding var showMe: Bool
var body: some View {
ZStack {
Color.red
Button {
showMe = true
} label: {
Text("Tap me")
.font(.largeTitle)
}
}
}
}
Post
Replies
Boosts
Views
Activity
It's trivial as follows.
import SwiftUI
struct LeftView: View {
var body: some View {
ZStack {
Color.green
}
}
}
I guess I've somehow worked it out as follows.
import SwiftUI
struct ContentView5: View {
@State private var larger = true
var body: some View {
VStack {
Circle()
.fill(Color.pink)
.frame(width: 150, height: 150)
.scaleEffect(larger ? 2 : 1)
.animation(.easeInOut(duration: 1).repeatForever(), value: larger)
}.onAppear {
larger = false
}
}
}
I've figured it out. You use UIApplicationDelegateAdaptor for iOS, NSApplicationDelegateAdaptor for Cocoa.
import SwiftUI
@main
struct MyCrazyApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
class AppDelegate: UIResponder, UIApplicationDelegate {
let fileManager = FileManager.default
}
In this fashion, you will access to fileManager in AppDelegate inside another View.
HStack + Spacer() should work unless you have NavigationLink inside the list.
The availability of UIStepper in Mac Catalyst depends on the deployment target. According to the doc, it requires iOS 13.0 or higher.
I've ended up creating a UIViewRepresentable struct so that I can set an NSAttributedString object to a UILabel object.
I've solved both of the issues with the following.
struct PickColorView: View {
@State var numberIndex: Int = 4
@State var textSizeIndex: Int = 0
let numbers: [Int] = [14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60]
var body: some View {
NavigationView {
Form {
Picker(selection: $numberIndex, label: Text("Numbers")) {
ForEach((0...20), id: \.self) {
Text("\($0)")
}
}.onChange(of: numberIndex) { newIndex in
print("Index: \(newIndex)")
}
Picker("Numbers", selection: $textSizeIndex) {
ForEach(0..<numbers.endIndex) { index in
let number = numbers[index]
Text("\(number)")
}
}.onChange(of: textSizeIndex) { newIndex in
print("Index: \(newIndex)")
}
}
.navigationBarTitle("Settings")
.navigationBarHidden(false)
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
You can do it if you hide the title bar.
I've figured out a way of doing it with StateObject. It's something like the following.
import SwiftUI
import Combine
struct ContentView2: View {
@State var disabled: Bool = false
@StateObject var delayMonitor = DelayMonitor()
@State private var showingAlert = false
var body: some View {
VStack {
Spacer()
Button("Tap to connect me") {
disabled = true
delayMonitor.start()
}
.font(.system(size: 24.0))
.disabled(disabled)
Spacer()
.frame(height: 30.0)
}.onChange(of: delayMonitor.failed) { newValue in
print("You've failed?: \(newValue)")
disabled = !newValue
showingAlert = newValue
}
.alert("Something is wrong...", isPresented: $showingAlert) {
Button("OK", role: .cancel) { }
}
}
}
class DelayMonitor: ObservableObject {
var timer = Timer()
var seconds: Double = 0.0
@Published var failed: Bool = false
func start() {
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { _ in
self.seconds += 1.0
if self.seconds == 5.0 { // arbitrary timeout
self.timer.invalidate()
DispatchQueue.main.async() {
[weak self] in
guard let strongSelf = self else { return }
strongSelf.failed = true
}
}
})
}
}
The onChange guy will let me know only if the value (delayMonitor.failed) has changed. Since its initial value is set to false, I'll get a call only if it changes to true.
I guess ObservableObject is a ticket to using Combine in SwiftUI. So I can write the following.
import SwiftUI
import Combine
struct ContentView: View {
@State var cancellables = Set<AnyCancellable>()
@StateObject var login = Login()
@State var canSave: Bool = false
var body: some View {
VStack {
Text("Login")
TextField("Enter username", text: $login.user)
TextField("Enter password", text: $login.pass)
Button("Save") {
}
.foregroundColor(canSave ? Color.orange : Color.gray)
.font(.system(size: 40.0))
.disabled(!canSave)
}
.padding(.horizontal, 40.0)
.onAppear {
Publishers.CombineLatest(login.$user, login.$pass)
.sink { completion in
print(completion)
} receiveValue: { (result0, result1) in
let bool = (result0.count > 3 && result1.count > 3)
canSave = bool
}.store(in: &cancellables)
}
}
}
class Login: ObservableObject {
@Published var user: String = ""
@Published var pass: String = ""
}
This is really good stuff.
I could do something like the following.
class ValidateLogin {
var good: Bool = false
let user: String
let pass: String
init(user: String, pass: String) {
self.user = user
self.pass = pass
}
func validateMe() -> Bool {
if user.count > 3 && pass.count > 3 {
good = true
}
return good
}
}
struct ContentView: View {
@State var userText: String = ""
@State var passText: String = ""
@State var canSave: Bool = false
var body: some View {
ZStack {
VStack {
TextField("Username", text: $userText) {
}.onChange(of: userText) { newValue in
let validateLogin = ValidateLogin(user: userText, pass: passText)
canSave = validateLogin.validateMe()
}
SecureField("Password", text: $passText) {
}.onChange(of: passText) { newValue in
let validateLogin = ValidateLogin(user: userText, pass: passText)
canSave = validateLogin.validateMe()
}
}.padding(.horizontal, 20.0)
}.onAppear {
//Publishers.CombineLatest($userText, $passText)
}
}
}
The code above doesn't involve Combine at all. I want to do it in a Combine way.
I've solved the problem by having NavigationView before ScrollView like the following.
ZStack {
VStack {
NavigationView {
ScrollView(.horizontal, showsIndicators: false) {
HStack(alignment: .top, spacing: 0) {
ForEach(horizonModels, id: \.self) { model in
if model.id == 0 {
NavigationLink(model.name) {
MenuView0()
}
.font(.system(size: 20.0))
.padding(.horizontal, 20.0)
.foregroundColor(Color.white)
} else {
NavigationLink(model.name) {
MenuView1()
}
.font(.system(size: 20.0))
.padding(.horizontal, 20.0)
.foregroundColor(Color.white)
}
}
}
}
.frame(height: 40.0)
.background(Color.orange)
}
}
}
That's kind of odd to me.
I've tested my sample app with Apple Sign In with two simulators. They don't go further after I enter my password. When I tested it for a macOS application two weeks ago, I ended up restarting my iMac. The same is true for an iOS sample that I created at the same time. I had to restart my iPhone. Anyway, in your case, I wouldn't be worried as long as it works on an actual device. Some features simply don't work with the simulator.
I guess the following is better. But I'm not completely satisfied.
// ViewController //
import UIKit
import Combine
class ViewController: UIViewController {
// MARK: - Variables
var cancellable: AnyCancellable?
private var cancellableSet: Set<AnyCancellable> = []
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
let urlStr = "https://api.github.com/repos/ReactiveX/RxSwift/events"
let viewModel = ViewModel(urlStr: urlStr, waitTime: 7.0)
viewModel.fetchData(urlText: viewModel.urlStr, timeInterval: viewModel.waitTime)
.sink { completion in
print("Done!")
} receiveValue: { dataModels in
print("Count: \(dataModels.count)")
}
.store(in: &cancellableSet)
}
}
// ViewModel //
import UIKit
import Combine
class ViewModel {
var anycancellables = Set<AnyCancellable>()
var urlStr: String
var waitTime: Double
init(urlStr: String, waitTime: Double) {
self.urlStr = urlStr
self.waitTime = waitTime
}
func fetchData(urlText: String, timeInterval: Double) -> Future<[DataModel], Error> {
return Future<[DataModel], Error> { promise in
let url = URL(string: urlText)!
var request = URLRequest(url: url)
request.timeoutInterval = timeInterval
let sessionConfiguration = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfiguration)
session.dataTask(with: request) { data, response, error in
if let error = error {
print("error: \(error.localizedDescription)")
promise(.failure("Failure" as! Error))
}
if let jsonData = data {
do {
let dataModels = try JSONDecoder().decode([DataModel].self, from: jsonData)
promise(.success(dataModels))
} catch {
print("Error while parsing: \(error)")
}
}
}.resume()
}
}
}