Post

Replies

Boosts

Views

Activity

Using Combine-Future to Fetch Server Data
I could do it with completionHandler, but I'm trying to get server data with Combine. The following is what I have. // UIViewController // import UIKit import Combine class ViewController: UIViewController { // MARK: - Variables 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: 2.0) viewModel.fetchData(urlText: viewModel.urlStr, timeInterval: viewModel.waitTime) .sink { completion in print("complete") } receiveValue: { dataSet in print("count: \(dataSet)") } .store(in: &cancellableSet) print("Yeah...") } } struct DataModel: Hashable, Decodable { let id: String let type: String } // ViewModel // import UIKit import Combine class ViewModel: NSObject { var cancellables = [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> { [weak self] promise in guard let strongSelf = self else { return } if let url = URL(string: urlText) { var request = URLRequest(url: url) request.timeoutInterval = timeInterval let sessionConfiguration = URLSessionConfiguration.default let publisher = URLSession(configuration: sessionConfiguration).dataTaskPublisher(for: request) publisher.sink { completion in print("complete") } receiveValue: { (data: Data, response: URLResponse) in do { let dataModels = try JSONDecoder().decode([DataModel].self, from: data) promise(.success(dataModels)) } catch { print("Error while parsing: \(error)") promise(.failure("Failure" as! Error)) } } .store(in: &strongSelf.cancellables) } else { promise(.failure("Failure" as! Error)) } } } } If I run it, I don't get an error. The app doesn't crash, either. The view controller doesn't deliver anything. What am I doing wrong? Muchos thankos.
6
0
1.8k
Dec ’21
Horizontal List with NavigationView and NavigationLink
I have a list with five rows as follows. import SwiftUI struct ContentView: View { @State var horizonModels = [MenuModel]() var body: some View { ZStack { Color.green NavigationView { List { ForEach(horizonModels, id: \.self) { model in if model.id == 0 { NavigationLink( destination: MenuView0(), label: { Text("\(model.name)") }) } else { NavigationLink( destination: MenuView1(), label: { Text("\(model.name)") }) } } } } } .ignoresSafeArea() .onAppear(perform: { horizonModels = [ MenuModel.init(id: 0, name: "Shopping"), MenuModel.init(id: 1, name: "Gift cards"), MenuModel.init(id: 2, name: "Video"), MenuModel.init(id: 3, name: "Music"), MenuModel.init(id: 4, name: "Account") ] }) } } struct MenuModel: Hashable, Identifiable { let id: Int let name: String } struct MenuView0: View { var body: some View { Text("Menu 0") } } struct MenuView1: View { var body: some View { Text("Menu 1") } } And I get something like the following screenshot. Well, I actually want to create a horizontal list like the screenshot below. I could do it except that I am not able to use NavigationView and NavigationLink instead of ScrollView and HStack. So how can I make a horizontally-scrollable list with NavigationView and NavigationLink? Muchos thankos.
1
0
1.1k
Dec ’21
Publishers.CombineLatest in SwiftUI
I've been using Combine with UIKit and Cocoa. The following is a simple example. import UIKit import Combine class ViewController: UIViewController { // MARK: - Variables private var cancellableSet: Set<AnyCancellable> = [] @Published var loginText: String = "" @Published var passwordText: String = "" // MARK: - IBOutlet @IBOutlet weak var loginField: UITextField! @IBOutlet weak var passwordField: UITextField! // MARK: - Life cycle override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: loginField) .sink { result in if let textField = result.object as? UITextField { if let text = textField.text { self.loginText = text } } } .store(in: &cancellableSet) NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: passwordField) .sink { result in if let textField = result.object as? UITextField { if let text = textField.text { self.passwordText = text } } } .store(in: &cancellableSet) Publishers.CombineLatest($loginText, $passwordText) .sink { (result0, result1) in if result0.count > 3 && result1.count > 3 { print("You are good") } else { print("No way!!!") } } .store(in: &cancellableSet) } } Now, I want to use Combine with SwiftUI. The following is SwiftUI equivalent, so far. import SwiftUI import Combine struct ContentView: View { @State var anycancellables = Set<AnyCancellable>() @State var userText: String = "" @State var passText: String = "" @State var canSave: Bool = false var body: some View { ZStack { VStack { Color.white }.onTapGesture { UIApplication.shared.endEditing() } VStack { TextField("Username", text: $userText) { }.onChange(of: userText) { newValue in } SecureField("Password", text: $passText) { }.onChange(of: passText) { newValue in } Spacer() .frame(height: 20.0) Button("Save") { print("Saved...") } .foregroundColor(canSave ? Color.black : Color.gray) .font(.system(size: 32.0)) .disabled(!canSave) }.padding(.horizontal, 20.0) } } } So where does Combine fit into the code? I want to enable the Save button if text counts of loginText and passwordText are both greater than 3, which is done at the top with UIKit. Muchos thankos.
3
0
2.2k
Jan ’22
Showing Alert or Sheet after Some Delay?
In the following lines of code, I tap a button and an alert message will appear. I wonder if I can change the code such that the alert message will appear after some time, say, 5 seconds? During this period of delay, I want to determine whether or not the app should show an alert message. import SwiftUI import Combine struct ContentView: View { @State var cancellables = Set<AnyCancellable>() @State var disabled: Bool = false @State private var showingAlert = false let timeInterval: Double = 5.0 var body: some View { VStack { Spacer() Button("Tap me") { showingAlert = true disabled = true // some Combine work // } .font(.system(size: 24.0)) .disabled(disabled) .alert("GGGG", isPresented: $showingAlert) { /* showing alert after timeInterval */ } } } } I could do it in UIKit, but I'm not sure if that's possible in SwiftUI. Muchos thankos.
1
0
2.4k
Jan ’22
Picker with Unexpected Results
I'm playing with Picker and have come up with some unexpected results. As shown below, I have three pickers in a form. struct PickColorView: View { @State var numberIndex: Int = 4 @State var textSizeIndex: Int = 0 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(selection: $textSizeIndex, label: Text("textSizeTitle")) { ForEach([14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60], id: \.self) { textSize in Text("\(textSize)") } }.onChange(of: textSizeIndex) { newIndex in print("Index: \(newIndex)") } } .navigationBarTitle("Settings") .navigationBarHidden(false) } .navigationViewStyle(StackNavigationViewStyle()) } } Well, if I run it, the top picker shows its initial selection (4) while the other doesn't. I wonder why? The following is a screenshot. (Please ignore the top picker appearing in the screenshot). If I go ahead and select one with the bottom picker, I end up with an actual value instead of the index. So if I select 18 (Please see the screenshot below.), I expect to get 2 with the onChange thing. But I get 18, instead. So how can I receive the index? Muchos thankos.
2
0
465
Jan ’22
Syntax-highlighting a String with Multiple Keys
I need to highlight a string with certain keys. The base string may contain one or more occurrences of a key. Test 1: I use code based on this web site . import SwiftUI struct HighlightView: View { let baseText : String = "President's nearly $2 trillion proposed healthcare, climate, and social spending plan has been floundering for over a month since Mr. Williams came out against the bill in late December. That left the party with little to show after six months of negotiations with the conservative Democratic holdout.\n\nHe later left the door cracked open to future negotiations on a separate plan. Mr. Williams has spoken favorably about some chunks of the bill, including a program to establish universal pre-K and climate spending measures.\n\nMr. Williams wields outsized influence over the Democratic Party's agenda since the Senate is split 50-50 between both parties. Senate Democrats can't pass the package without his support, leaving them with no option but to eventually tailor a separate bill that addresses his concerns about its possible impact on the national debt and inflation.\n\nMr. Williams, meanwhile, appears to be focusing most of his time and energy leading a bipartisan group of senators working on election reform proposals including modernizing and updating the Electoral Count Act of 1887." let keys = ["Democratic", "Williams", "about"] var body: some View { ZStack { Text(baseText) { str in for i in 0..<keys.count { let key = keys[i] if let range = str.range(of: key) { str[range].foregroundColor = .pink } } }.foregroundColor(Color.gray).font(.system(size: 18.0)) } } } extension Text { init(_ string: String, configure: ((inout AttributedString) -> Void)) { var attributedString = AttributedString(string) /// create an `AttributedString` configure(&attributedString) /// configure using the closure self.init(attributedString) /// initialize a `Text` } } The following screenshot shows the result. It handles multiple keys. But it highlights one occurrence for each key. Test 2: I use code based on some web site, which I'm not permitted to show according to this system. import SwiftUI struct HighlightView2: View { let baseText : String = "President's nearly $2 trillion proposed healthcare, climate, and social spending plan has been floundering for over a month since Mr. Williams came out against the bill in late December. That left the party with little to show after six months of negotiations with the conservative Democratic holdout.\n\nHe later left the door cracked open to future negotiations on a separate plan. Mr. Williams has spoken favorably about some chunks of the bill, including a program to establish universal pre-K and climate spending measures.\n\nMr. Williams wields outsized influence over the Democratic Party's agenda since the Senate is split 50-50 between both parties. Senate Democrats can't pass the package without his support, leaving them with no option but to eventually tailor a separate bill that addresses his concerns about its possible impact on the national debt and inflation.\n\nMr. Williams, meanwhile, appears to be focusing most of his time and energy leading a bipartisan group of senators working on election reform proposals including modernizing and updating the Electoral Count Act of 1887." let keys = ["Democratic", "Williams", "about"] var body: some View { ZStack { ForEach(keys, id: \.self) { key in highlightedText(baseText: baseText, match: key, highlightColor: Color.orange) .foregroundColor(Color.gray).font(.system(size: 19.0)) } } } func highlightedText(baseText: String, match: String, highlightColor: Color) -> Text { guard !baseText.isEmpty && !match.isEmpty else { return Text(baseText) } var result: Text! let components = baseText.components(separatedBy: match) for i in components.indices { result = (result == nil ? Text(components[i]) : result + Text(components[i])) if i != components.count - 1 { result = result + Text(match).foregroundColor(highlightColor) } } return result ?? Text(baseText) } } This one handles multiple occurrences. But I can only use one key at a time. The following screenshot shows the result. Do you have a better idea in highlighting a string with multiple keys? Muchos thankos for reading.
1
0
505
Feb ’22
Where and How to Create FileManager as a Singleton?
When I write code with UIKIt or Cocoa, I usually create and use FileManager.default in AppDelegate or a base view controller. In Cocoa, for example, I would write something like the following. import Cocoa @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { let defaultFileManager = FileManager.default func applicationDidFinishLaunching(_ aNotification: Notification) { } func applicationWillTerminate(_ aNotification: Notification) { NSApp.terminate(nil) } } import Cocoa class HomeViewController: NSViewController { let appDelegate = (NSApp.delegate as! AppDelegate) override func viewDidLoad() { super.viewDidLoad() if appDelegate.defaultFileManager.fileExists(atPath: some file path) { } } } So my question is where I can create FileManager.default so that I use it under different Views in SwiftUI? Muchos thankos.
2
0
615
Feb ’22
Using Subclassed UIViewController
I'm trying to subclass UIViewController. And I've written the following. import UIKit class BaseViewController: UIViewController { let titleText: String init(titleText: String) { self.titleText = titleText super.init(nibName: nil, bundle: nil) } required init?(coder aDecoder: NSCoder) { self.titleText = "" super.init(coder: aDecoder) setup() } override func viewDidLoad() { super.viewDidLoad() } // MARK: - Setup func setup() { print("GGG: \(titleText)") let navBar = navigationController!.navigationBar navBar.barTintColor = UIColor.yellow let atext = NSMutableAttributedString(string: titleText) atext.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor.black, range: NSMakeRange(0, atext.length)) atext.addAttribute(NSAttributedString.Key.strokeColor, value: UIColor.gray, range: NSMakeRange(0, atext.length)) atext.addAttribute(NSAttributedString.Key.strokeWidth, value: NSNumber.init(value: -1.0), range: NSMakeRange(0, atext.length)) let titleLabel: UILabel = UILabel(frame: CGRect(origin: CGPoint(x: 15.0, y: 0), size: CGSize(width: 320.0 - 120.0, height: 44.0))) titleLabel.attributedText = atext titleLabel.textAlignment = NSTextAlignment.center titleLabel.font = UIFont(name: "Helvetica", size: 24.0) self.navigationItem.titleView = titleLabel } } And I subclass it with a view controller named HomeViewController as follows. import UIKit class HomeViewController: BaseViewController { override init(titleText: String) { super.init(titleText: "Jim") } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } override func viewDidLoad() { super.viewDidLoad() } } But the setup method in BaseViewController never gets the titleText variable ("Jim") from HomeViewController. What am I doing wrong? Thanks.
Topic: UI Frameworks SubTopic: UIKit Tags:
3
0
667
Feb ’22
How to Update a UILabel Object with MainActor
I guess I was in coma, and I didn't learn of MainActor till today. So I have the following lines of code to test MainActor. import UIKit class ViewController: UIViewController { // MARK: - IBOutlet @IBOutlet weak var label: UILabel! // MARK: - IBAction @IBAction func buttonTapped(_ sender: UIButton) { Task { do { let bool = try await asyncWaitMakeLabelChanges() if bool { print("I'm done!") } } catch { print("\(error.localizedDescription)") } } } func asyncWaitMakeLabelChanges() async throws -> Bool { for i in 0..<5 { let text = String(i) label.text = text // UILabel.text must be used from main thread only print(text) sleep(1) } return true } } As I expect, I get the purple main thread checker error at the line where I update the label. That's good. And I've changed the code as follows. import UIKit class ViewController: UIViewController { // MARK: - IBOutlet @IBOutlet weak var label: UILabel! // MARK: - IBAction @IBAction func buttonTapped(_ sender: UIButton) { Task { do { let bool = try await asyncWaitMakeLabelChanges() if bool { print("I'm done!") } } catch { print("\(error.localizedDescription)") } } } func asyncWaitMakeLabelChanges() async throws -> Bool { for i in 0..<5 { let text = String(i) Task { @MainActor in label.text = text } print(text) sleep(1) } return true } } Okay. The app won't crash. But the label won't get updated every second. It will finally display the number (4) when the count reaches 4. So my question is why not? I could change my code as follows to update my label every second. func asyncWaitMakeLabelChanges() async throws -> Bool { for i in 0..<5 { let text = String(i) DispatchQueue.main.async() { [weak self] in self?.label.text = text } print(text) sleep(1) } return true } So why would I want to use MainActor?
Topic: UI Frameworks SubTopic: UIKit Tags:
1
0
1.3k
Feb ’22
'animation' was deprecated in iOS 15.0
Hmm... I don't quite get it. How can I get rid of the deprecation warning in the following case? import SwiftUI struct ContentView: View { @State var isAnimating = false var body: some View { Circle() .fill(Color.pink) .frame(width: 150, height: 150) .scaleEffect(isAnimating ? 0.5 : 1.0) .animation(Animation.easeIn(duration: 1.0).repeatForever()) .onAppear { self.isAnimating = true } } } The following could work except that I have to tap the circle once. import SwiftUI struct ContentView: View { @State var isAnimating = true var body: some View { Circle() .fill(Color.pink) .frame(width: 150, height: 150) .scaleEffect(isAnimating ? 1 : 0.5) .animation(Animation.easeIn(duration: 3.0).repeatForever(), value: isAnimating) .onTapGesture { isAnimating.toggle() } .onAppear { isAnimating = false isAnimating.toggle() } } } Muchos thankos.
1
0
3.5k
Feb ’22
Telling a View to show a Dialog from Another
I was quite active in writing code in SwiftUI several months ago. I've forgotten how to use an ObservedObject object to channel a variable between two Views. Anyway, I need to show a dialog over ContentView when I tap a button that is shown over another (RightView). The following is my code. // ContentView.swift // import SwiftUI class ObserveMonster: ObservableObject { @Published var showDialog = false } struct ContentView: View { @ObservedObject var observeManiac: ObserveMonster 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() .frame(width: geo.size.width / 2.0, height: geo.size.height, alignment: .trailing) } ShowDialogView(isShowing: observeManiac.showDialog) { } .frame(width: 500, height: 600, alignment: .center) .cornerRadius(10.0) } } } } struct ShowDialogView<Content: View>: View { let isShowing: Bool @ViewBuilder let content: () -> Content var body: some View { Group { if isShowing { Color.blue } } .animation(.default, value: isShowing) } } // RightView.swift // import SwiftUI struct RightView: View { @StateObject var observeManiac = ObserveMonster() var body: some View { ZStack { Color.red Button { observeManiac.showDialog.toggle() } label: { Text("Tap me") .font(.largeTitle) } } } } When I tap the button, the dialog (ShowDialogView) is no show. Does anybody now what I'm doing wrong? Thanks a million.
3
0
567
Mar ’22
Passing a Variable Between Two Unrelated Views Without Binding
My ContentView has one View (RightView) inside. Tapping the button over RightView, the app will pass a boolean value to ContentView. struct ContentView: View { @ObservedObject var monster: MonsterObservable var body: some View { GeometryReader { geo in ZStack { HStack(spacing: 0.0) { RightView(showMe: $monster.showDialog) .frame(width: geo.size.width, height: geo.size.height) } ShowDialogView(isShowing: $monster.showDialog) { } .frame(width: 500, height: 600, alignment: .center) .cornerRadius(10.0) } } } } struct RightView: View { @Binding var showMe: Bool var body: some View { ZStack { Color.red Button { showMe = true } label: { Text("Tap me") .font(.largeTitle) } } } } class MonsterObservable: ObservableObject { @Published var showDialog = false } struct ShowDialogView<Content: View>: View { @Binding var isShowing: Bool @ViewBuilder let content: () -> Content var body: some View { Group { if isShowing { ZStack { Color.brown VStack { Spacer() Button { isShowing = false } label: { Text("Close me") .font(.largeTitle) }.padding([.top, .bottom], 100.0) } } } } } } So the code above works. If I tap the button over RightView, a small dialog (ShowDialogView) will appear. Currently, ContentView and RightView are bound. That's not exactly what I need. How can I pass a boolean value from RightView to ContentView without Binding where ContentView is the following? struct ContentView: View { @ObservedObject var monster: MonsterObservable var body: some View { GeometryReader { geo in ZStack { HStack(spacing: 0.0) { RightView() .frame(width: geo.size.width, height: geo.size.height) } ShowDialogView(isShowing: $monster.showDialog) { } .frame(width: 500, height: 600, alignment: .center) .cornerRadius(10.0) } } } }
1
0
600
Mar ’22
Getting an Updated Value Through @EnvironmentObject
I have a simple project as follows. import SwiftUI class GameSettings: ObservableObject { @Published var score: Int = 100 } struct ContentView: View { @StateObject var settings = GameSettings() var body: some View { GeometryReader { geo in ZStack { HStack(spacing: 0.0) { RightView().environmentObject(GameSettings()) .frame(width: geo.size.width / 2.0, height: geo.size.height) Spacer() } VStack { HStack { Spacer() Button { print("\(settings.score)") } label: { Text("Print") .font(.largeTitle) }.padding(.trailing, 40.0) } Spacer() } } } } } struct RightView: View { @EnvironmentObject var settings: GameSettings var body: some View { ZStack { Color.red Button { settings.score += 100 } label: { Text("Change") .font(.largeTitle) } }.environmentObject(settings) } } So the score is supposed to increase by 100 if I tap the button over the red area. And I want to print the latest value by tapping the Print button at the top-right corner. But it will remain at 100. What am I doing wrong? And can I achieve my goal without using an @ObservedObject variable? Thanks.
2
0
508
Mar ’22
Deleting a View Instance with the Tap of a Button
I have a simple project where I have a UUID string followed by a tap button as shown below. If one taps the Add me, the app will list a new instance of a View (KeywordRow). The following is what I have. import SwiftUI struct ContentView: View { @ObservedObject var monster: Monster var body: some View { VStack { Button { monster.items.append(Keyword()) } label: { Text("Add me!") }.padding(.vertical, 10.0) ForEach($monster.items) { item in KeywordRow(id: item.id) } } } } // MARK: - ObservableObject class Monster: ObservableObject { @Published var items = [Keyword]() } // MARK: - Keyword struct Keyword: Identifiable { var id = UUID() } struct KeywordRow: View { @Binding var id: UUID var body: some View { VStack { HStack { Text("ID: \(id)") Button { /* ------ Delete ------ */ } label: { Text("Delete") } } } } } My question is how I can let the app delete the corresponding instance when I tap the Delete button? I have an ObservedObject variable, which I haven't used. Thanks.
3
0
1.4k
Apr ’22
TextField Binding
I have an array of a model with just a single string with which I want to create instances of TextField. And I get an error for the TextField string binding. I know that is wrong. But how can fix it so that I can use textModel.name as a Binding? import SwiftUI struct ContentView: View { @State var textModels = [TextModel]() var body: some View { HStack { ForEach(textModels.indices, id: \.self) { index in let textModel = textModels[index] TextField("", text: textModel.name) // <----- Cannot convert value of type 'String' to expected argument type 'Binding<String>' } }.background(Color.green) .onAppear { textModels.append(TextModel(name: "Jim Thorton")) textModels.append(TextModel(name: "Susan Murphy")) textModels.append(TextModel(name: "Tom O'Donnell")) textModels.append(TextModel(name: "Nancy Smith")) } } } struct TextModel: Hashable { let name: String } Thanks.
3
0
1.1k
Apr ’22