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?
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
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.
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.
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)
}
}
}
}
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.
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.
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.
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.
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.
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.
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.
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.
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.
I have downloaded the UniversalMac_13.0_22A379_Restore.ipsw file. How do we use this file to install the new OS? Thanks.
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.
}
}