Post

Replies

Boosts

Views

Activity

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
566
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
599
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
507
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
Showing Multiple Instances of View and Showing Results from Them
I have the following lines of code to show multiple instances of View (MyTextView) import SwiftUI struct ContentView: View { @State var myTextViews = [MyTextView]() var body: some View { VStack { Button { } label: { Text("Show me your current text strings") }.padding(.vertical, 10.0) VStack { ForEach(0 ..< myTextViews.count, id: \.self) { _ in MyTextView() } } Button { myTextViews.append(MyTextView()) } label: { Text("Add me!") }.padding(.vertical, 10.0) } } } struct MyTextView: View { @State var text = "" var body: some View { ZStack { TextField("Enter some text", text: $text) }.padding(.horizontal, 50.0) } } According to the screenshot, I have three instances, each of which contains TextField. After I tap the top button (Show me your current...), I want to show the result from each TextField. How can I do that? Thanks.
5
0
1.2k
Apr ’22
Binding<String>, set, get?
I have three sets of Text and TextField. And I need to filter each TextField entry. I have gotten a function to filter the TextField entry from this website (https://zenn.dev/yorifuji/articles/swiftui-textfield-filter). Finally, I have the following lines of code. import SwiftUI struct ContentView: View { @State var username = "" @State var password = "" @State var tenantID = "" var body: some View { VStack { makeForm(label: "Username: ", placeHolder: "123456", text: $username) makeForm(label: "Password: ", placeHolder: "abcdefg", text: $password) makeForm(label: "Shop ID: ", placeHolder: "123456", text: $tenantID) }.padding(.horizontal, 40.0) } @ViewBuilder private func makeForm(label: String, placeHolder: String, text: Binding<String>) -> some View { HStack { let newText = Binding<String>( get: { text.wrappedValue }, set: { filter(value: $0) } ) Text(label) TextField(placeHolder, text: newText) } } func filter(value: String) -> String { let validCodes = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" let sets = CharacterSet(charactersIn: validCodes) return String(value.unicodeScalars.filter(sets.contains).map(Character.init)) } } Well, I don't know how to use the Binding with get and set, which I believe is what I need. Yet, I get a warning at the following line. set: { filter(value: $0) } What I need to do is set the filtered value to each TextField. What am I doing wrong? Thanks.
6
0
1.3k
May ’22
System requirements for Xcode 14
I'm a bit confused about the minimum macOS version required to run Xcode 14. I don't know what the minimum macOS version to run it. In the meantime, it seems that macOS 13 beta requires a Mac computer with the Apple silicon. As for Xcode 14 beta, its details say "Xcode 14 beta includes everything you need to create amazing apps for all Apple platforms. It includes SDKs for iOS 16, iPadOS 16, tvOS 16, watchOS 9, and macOS 13." What does that mean as far as macOS 13 is concerned? Do we need a Mac with the Apple silicon to run Xcode 14? Thanks.
1
0
8.1k
Jun ’22
Observing a Call from ObservableObject without onChange
I have a simple case as follows. class Monster: ObservableObject { static let shared = Monster() @Published var selectionChanged = false } struct ContentView: View { @ObservedObject var monster = Monster.shared @State private var isOn = false var body: some View { VStack { Button { monster.selectionChanged.toggle() } label: { Text("Tap me") } .padding(.vertical, 60.0) SecondView() } } } struct SecondView: View { @StateObject var monster = Monster.shared var body: some View { VStack { Text("Hello") }.onChange(of: monster.selectionChanged) { _ in print("GGGG") } } } So SecondView receives a call from Monster with through onChange.. Is there a simpler approach where SecondView receives a call without it? Thanks.
1
0
1.2k
Aug ’22
Not Allowing Row Selection in List
In UIKit, it's just the matter of setting table view's allowsSelection to false if you don't want to allow selection. If each row has a button, it can still be clicked on. Can we do that in SwiftUI? I have the following simple SwiftUI project. import SwiftUI struct ContentView: View { @State private var fruits = ["Apple", "Banana", "Mango", "Watermelon", "Pineapple", "Strawberry", "Orange"] @State private var isEditable = true var body: some View { List { ForEach(fruits, id: \.self) { fruit in HStack { Text(fruit) .onDrag { return NSItemProvider() } Spacer() Button { print("Hello") } label: { Text("Tap me") } } } .onMove(perform: move) } .onTapGesture(perform: { return }) .listStyle(.plain) } func move(from source: IndexSet, to destination: Int) { fruits.move(fromOffsets: source, toOffset: destination) withAnimation { isEditable = false } } } The tap gesture prevents row interaction. So I won't even be able to tap the button. How can I disable row selection while allowing interaction inside the list row? List { } .onAppear { UITableView.appearance().allowsSelection = false UITableViewCell.appearance().selectionStyle = .none } The lines of code above don't work, either. Thanks.
1
0
2.2k
Aug ’22
Sorting Array of Dictionaries with Exceptions?
I have an array of dictionaries. And I need to sort this array in an ascending order. someArray = someArray.sorted { lValue, rValue in lValue.categoryName < rValue.categoryName } So one of the dictionaries is categoryName. Now, I have a case where the category name of an array element is something specific, say 'price,' then that element must be listed at the end as an exception to the rule above. Is that possible? Thanks.
2
0
709
Sep ’22
AVCaptureSession startRunning crash
I have revisited AVCaptureSession in UIKit to capture a snapshot with the FaceTime camera. And my sample app will crash when AVCaptureSession starts running. Does anyone know how to fix it? The console says the following purple warning. -[AVCaptureSession startRunning] should be called from background thread. Calling it on the main thread can lead to UI unresponsiveness import UIKit import AVFoundation class CaptureViewController: UIViewController, AVCapturePhotoCaptureDelegate { var captureSession: AVCaptureSession! var cameraDevices: AVCaptureDevice! var imagePhotoOutput: AVCapturePhotoOutput! enum CameraCase { case front case back } // MARK: - IBAction @IBAction func selectTapped(_ sender: UIButton) { snapPicture() } // MARK: - Life cycle override func viewDidLoad() { super.viewDidLoad() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) prepareCamera(cameraCase: .front) } // MARK: - Camera func prepareCamera(cameraCase: CameraCase) { /* removing existing layers */ if let sublayers = self.view.layer.sublayers { for sublayer in sublayers { if sublayer.isKind(of: AVCaptureVideoPreviewLayer.self) { sublayer.removeFromSuperlayer() } } } /* creating a capture session */ captureSession = AVCaptureSession() if cameraCase == .front { guard let device = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaType.video, position: .back).devices.first else { return } let videoInput = try? AVCaptureDeviceInput(device: device) if captureSession.canAddInput(videoInput!) { captureSession.addInput(videoInput!) imagePhotoOutput = AVCapturePhotoOutput() // setting output destination captureSession.addOutput(imagePhotoOutput) // adding photo output to session } } else { guard let device = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaType.video, position: .front).devices.first else { return } let videoInput = try? AVCaptureDeviceInput(device: device) if captureSession.canAddInput(videoInput!) { captureSession.addInput(videoInput!) imagePhotoOutput = AVCapturePhotoOutput() // setting output destination captureSession.addOutput(imagePhotoOutput) // adding photo output to session } } /* creating a capture layer */ let captureVideoLayer: AVCaptureVideoPreviewLayer = AVCaptureVideoPreviewLayer.init(session: captureSession) captureVideoLayer.frame = CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height) captureVideoLayer.videoGravity = AVLayerVideoGravity.resizeAspect /* adding video capture layer to the view layer */ self.view.layer.addSublayer(captureVideoLayer) /* starting capture session */ captureSession.startRunning() //<<<<<<<<<<<<<<<<<<<<<<<<<<< The console shows a purple warning here. } }
3
0
12k
Dec ’22