Let me say that I have three structs that are sequentially connected.
ContentView -> FirstView -> SecondView
And I want to make a call from SecondView to ContentView with a button tap. So I have the following lines of code.
import SwiftUI
struct ContentView: View {
@State var goToFirst = false
var body: some View {
NavigationStack {
VStack {
NavigationLink {
FirstView(callBack: {
sayHello()
}, goToSecond: $goToFirst)
} label: {
Text("Go to First")
}
}
}
.navigationDestination(isPresented: $goToFirst) {
}
}
func sayHello() {
print("Hello!")
}
}
struct FirstView: View {
@State var callBack: (() -> Void)?
@Binding var goToSecond: Bool
var body: some View {
VStack {
Button("Go to Second") {
goToSecond.toggle()
}
}
.navigationDestination(isPresented: $goToSecond) {
SecondView(callBack: callBack)
}
}
}
struct SecondView: View {
@State var callBack: (() -> Void)?
var body: some View {
VStack {
Button("Tap me to make a call to ContentView") {
callBack?()
}
}
}
}
If I tap the button in SecondView, my ContentView will receive a call and call the sayHello function. Since ContentView and SecondView are not directly connected with each other, they have to through FirstView in this case. I wonder if there's a better or easier approach in having SecondView make a call to ContentView? In UIKit and Cocoa, you can make a delegate call to a distant class even when two classes are not directly connected with other. Using the notification is another option. In SwiftUI, I suppose you don't use either of them. Muchos thankos.
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
I have a sample macOS app that I'm working on. I can run the exactly same lines of code below for iOS. For now, I'm running code for macOS since I can just press Command + z to undo the last action.
Anyway, I have two Text View objects. Since TextView has the DragGesture gesture, I am able to freely move either of them. And I want to undo and redo their positions. So the following is what I have.
import SwiftUI
struct ContentView: View {
@State var textViews: [TextView] = [TextView(text: "George"), TextView(text: "Susan")]
var body: some View {
VStack {
ForEach(textViews, id: \.id) { textView in
textView
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct TextView: View {
@Environment(\.undoManager) var undoManager
@StateObject private var undoModel = UndoViewModel()
@State private var dragOffset: CGSize = .zero
@State private var position: CGSize = .zero
let id = UUID()
let text: String
init(text: String) {
self.text = text
}
var body: some View {
ZStack {
Text(text)
.fixedSize()
.padding(.vertical, 10)
.offset(x: dragOffset.width + position.width, y: dragOffset.height + position.height)
.gesture(
DragGesture()
.onChanged {
self.dragOffset = $0.translation
}
.onEnded( { (value) in
self.position.width += value.translation.width
self.position.height += value.translation.height
self.dragOffset = .zero
undoModel.registerUndo(CGSize(width: position.width, height: position.height), in: undoManager)
})
)
}
}
}
class UndoViewModel: ObservableObject {
@Published var point = CGSize.zero
func registerUndo(_ newValue: CGSize, in undoManager: UndoManager?) {
let oldValue = point
undoManager?.registerUndo(withTarget: self) { [weak undoManager] target in
target.point = oldValue // registers an undo operation to revert to old text
target.registerUndo(oldValue, in: undoManager) // this makes redo possible
}
undoManager?.setActionName("Move")
point = newValue // update the actual value
}
}
Well, if I press Command + z after moving one of them, it won't return to the last position. What am I doing wrong? Muchos thankos.
I'm trying to change the locale of an app with Picker as follows.
import SwiftUI
@main
struct LocaleSwitchCrazyMamaApp: App {
var body: some Scene {
WindowGroup {
let lanSetting = LanguageSetting()
ContentView()
.environmentObject(lanSetting)
.environment(\.locale, lanSetting.locale)
}
}
}
class LanguageSetting: ObservableObject {
@Published var locale = Locale(identifier: "en")
}
import SwiftUI
struct ContentView: View {
@State private var segmentSelection = 0
@EnvironmentObject var languageSetting: LanguageSetting
var body: some View {
VStack {
Text(NSLocalizedString("Hello", comment: ""))
.padding(.vertical, 20)
Picker("Language", selection: $segmentSelection) {
Text("English").tag(0)
Text("Japanese").tag(1)
Text("French").tag(2)
}
.frame(width: 200)
.pickerStyle(.segmented)
.onChange(of: segmentSelection) {newValue in
if newValue == 0 {
languageSetting.locale = Locale(identifier: "en")
} else if newValue == 1 {
languageSetting.locale = Locale(identifier: "ja")
} else {
languageSetting.locale = Locale(identifier: "fr")
}
}
}
.padding()
}
}
In addition, I have three locale versions like the following
"Hello" = "Hello"; // en.lproj
"Hello" = "Bonjour"; //fr.lproj
"Hello" = "こんにちは"; // ja.lproj
As long as I run the app on a simulator, the language of the Hello text won't change when tap any of the segments. What am I doing wrong?
Muchos thankos
I have a test app that is supposed to list a bunch of iTunes music records. I have existing lines of code that successfully load data. Now, I'm adding a loading state to them. As a result, I have the following lines code.
import SwiftUI
struct iTunesView: View {
@StateObject var viewModel = iTunesViewModel()
var body: some View {
switch viewModel.state {
case .idle: EmptyView()
case .loading: ProgressView()
case .loaded(let results):
List {
ForEach(results, id: \.self) { result in
Text("\(result.trackId)")
Text(result.trackName)
.lineLimit(0)
}
}
case .failed(let error):
Text(error.localizedDescription)
}
}
}
@MainActor
class iTunesViewModel: ObservableObject {
enum LoadingState {
case idle
case loading
case loaded([iTunesResult])
case failed(Error)
}
@Published var state: LoadingState = .idle
init() {
state = .loading
Task {
await fetchMusic()
}
}
func fetchMusic() async {
guard let url = URL(string: "https://itunes.apple.com/search?term=classical+music&entity=song") else {
state = .failed(URLError(.badURL))
return
}
do {
let urlRequest = URLRequest(url: url, timeoutInterval: 1.0)
let (data, _) = try await URLSession.shared.data(for: urlRequest)
let music = try JSONDecoder().decode(iTunesResponse.self, from: data)
self.state = .loaded(music.results)
print("\(music.results)")
} catch {
state = .failed(error)
}
}
}
struct iTunesResponse: Codable {
let resultCount: Int
let results: [iTunesResult]
}
struct iTunesResult: Codable, Hashable {
var trackId: Int
var trackName: String
var collectionName: String
}
enum iTunesError: Error {
case badURL
case decoding
case invalidHTTPResponse
case badData(statusCode: Int)
case badRequest(statusCode: Int)
case redirection(statusCode: Int)
case server(statusCode: Int)
case error(String)
}
For some reason, the app just shows a spinner although it reaches the print line in the fetchMusci function and print the string data. I wonder what I'm doing wrong? With the code lines above, an app is fully functional except it will show the progress guy, you know? I've made changes after reading this Stack overflow topic. Thanks.
I'm just playing with Combine, and I want to limit the number of letters that the text field can show. So I have the following lines of code.
import SwiftUI
struct ContentView: View {
@State var messageText: String = ""
var body: some View {
let bindingMessage = Binding {
messageText
} set: {
messageText = String($0.prefix(10))
print(String($0.prefix(10)))
}
Form {
TextField("Message", text: bindingMessage)
}
}
}
I'm expecting that the text field will show only the first 10 letters. But, as the screenshot below indicates, it shows the entire string. What am I doing wrong? Muchos Thankos. It sounds basic. Ugghhh...
I am perplexed as to how to use async await. In the following example, I don't use GCD or performSelector(inBackground:with:). The view controller is NSViewController, but it doesn't make any difference if it's NSViewController or UIViewController.
import Cocoa
class ViewController: NSViewController {
func startWriteImages() {
Task{
let bool = await startWriteImagesNext()
if bool {
print("I'm done!")
}
}
}
func startWriteImagesNext() async -> Bool {
// pictures is a path to a folder in the sandbox folder
// appDelegate.defaultFileManager is a variable pointing to FileManager.default in AppDelegate
let pictURL = URL(fileURLWithPath: pictures)
if let filePaths = try? self.appDelegate.defaultFileManager.contentsOfDirectory(atPath: pictURL.path) {
for file in filePaths {
let fileURL = pictURL.appending(component: file)
if self.appDelegate.defaultFileManager.fileExists(atPath: fileURL.path) {
let newURL = self.folderURL.appending(component: file)
do {
try self.appDelegate.defaultFileManager.copyItem(at: fileURL, to: newURL)
} catch {
print("Ugghhh...")
}
}
}
return true
}
return false
}
func startWriteImagesNext2() async -> Bool {
let pictURL = URL(fileURLWithPath: pictures)
if let filePaths = try? self.appDelegate.defaultFileManager.contentsOfDirectory(atPath: pictURL.path) {
DispatchQueue.global().async() {
for file in filePaths {
let fileURL = pictURL.appending(component: file)
if self.appDelegate.defaultFileManager.fileExists(atPath: fileURL.path) {
let newURL = self.folderURL.appending(component: file)
do {
try self.appDelegate.defaultFileManager.copyItem(at: fileURL, to: newURL)
} catch {
print("Ugghhh...")
}
}
}
}
return true
}
return false
}
}
In the code above, I'm saving each file in the folder to user-selected folder (self.folderURL). And the application will execute the print guy only when work is done. Since it's heavy-duty work, I want to use CCD or performSelector(inBackground:with:). If I use the former (startWriteImagesNext2), the application will execute the print guy right at the beginning. I suppose I cannot use GCD with async. So how can I perform heavy-duty work? Muchos thankos.
A device running with the following lines of code can receive a message from a peripheral. In this manner, though, I can only receive messages from one peripheral since the service and characteristic IDs are hardcoded in CentralViewModel.swift. So my question is how I can observe messages from multiple peripherals. Thanks.
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationStack {
VStack {
NavigationLink(destination: CentralView()) {
Text("Central")
}
.buttonStyle(.borderedProminent)
.padding()
}
}
}
}
// CentralView.swift //
import SwiftUI
struct CentralView: View {
@StateObject var central: CentralViewModel = CentralViewModel()
var body: some View {
Text(central.message)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.padding(20)
.onDisappear {
central.stopAction()
}
}
}
// CentralViewModel.swift //
import Foundation
import CoreBluetooth
class CentralViewModel: NSObject, ObservableObject {
@Published var message: String = ""
var serviceUUID: CBUUID!
var characteristicUUID: CBUUID!
var centralManager: CBCentralManager!
var discoveredPeripheral: CBPeripheral?
var transferCharacteristic: CBCharacteristic?
var writeIterationsComplete = 0
//var connectionIterationsComplete = 0
let defaultIterations = 5
var data: Data = Data()
override init() {
super.init()
self.serviceUUID = CBUUID(string: "994F8A12-FE8E-4CCB-BD7B-1AE989A32853")
self.characteristicUUID = CBUUID(string: "F4BD0CA2-7581-40E2-A517-1CE275A3A749")
centralManager = CBCentralManager(delegate: self, queue: nil, options: [CBCentralManagerOptionShowPowerAlertKey: true])
}
func stopAction() {
centralManager.stopScan()
}
private func cleanup() {
guard let discoveredPeripheral = discoveredPeripheral, case .connected = discoveredPeripheral.state else { return }
for service in (discoveredPeripheral.services ?? [] as [CBService]) {
for characteristic in (service.characteristics ?? [] as [CBCharacteristic]) {
if characteristic.uuid == characteristicUUID && characteristic.isNotifying {
self.discoveredPeripheral?.setNotifyValue(false, for: characteristic)
}
}
}
centralManager.cancelPeripheralConnection(discoveredPeripheral)
}
private func writeData() {
guard let discoveredPeripheral = discoveredPeripheral, let transferCharacteristic = transferCharacteristic
else {
return
}
while writeIterationsComplete < defaultIterations && discoveredPeripheral.canSendWriteWithoutResponse {
writeIterationsComplete += 1
}
if writeIterationsComplete == defaultIterations {
discoveredPeripheral.setNotifyValue(false, for: transferCharacteristic)
}
}
}
extension CentralViewModel: CBCentralManagerDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .poweredOn:
print("Power on")
startScanningForPeripherals()
return
case .poweredOff :
print("Power off")
return
case .resetting:
print("Resetting")
return
case .unauthorized:
print("Unauthorized")
return
case .unknown:
print("Unknown")
return
case .unsupported:
print("Unsupported")
return
@unknown default:
print("An unknown central manager state has occurred")
return
}
}
func startScanningForPeripherals() {
self.centralManager.scanForPeripherals(withServices: [self.serviceUUID], options: nil)
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
guard RSSI.intValue >= -50 else {
return
}
if discoveredPeripheral != peripheral {
print("Peripheral discovered")
discoveredPeripheral = peripheral
centralManager.connect(peripheral, options: nil)
}
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
peripheral.delegate = self
peripheral.discoverServices([serviceUUID])
print("Service discovered")
}
}
extension CentralViewModel: CBPeripheralDelegate {
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
if error != nil {
cleanup()
return
}
guard let peripheralServices = peripheral.services else {
return
}
for service in peripheralServices {
peripheral.discoverCharacteristics([characteristicUUID], for: service)
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
if let error = error {
print("Error discovering characteristics: \(error.localizedDescription)")
cleanup()
return
}
guard let serviceCharacteristics = service.characteristics else {
return
}
for characteristic in serviceCharacteristics where characteristic.uuid == characteristicUUID {
transferCharacteristic = characteristic
peripheral.setNotifyValue(true, for: characteristic)
}
}
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
if let error = error {
print("Error changing notification state: \(error.localizedDescription)")
return
}
guard characteristic.uuid == characteristicUUID else {
return
}
if characteristic.isNotifying {
print("Notification began on \(characteristic)")
} else {
print("Notification stopped on \(characteristic). Disconnecting")
cleanup()
}
}
func peripheralIsReady(toSendWriteWithoutResponse peripheral: CBPeripheral) {
print("Peripheral is ready to send data to YOU!")
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
if let error = error {
print("Error discovering characteristics: \(error.localizedDescription)")
cleanup()
return
}
guard let characteristicData = characteristic.value,
let stringFromData = String(data: characteristicData, encoding: .utf8) else {
return
}
print("Received \(characteristicData.count) bytes: \(stringFromData)")
if stringFromData == "EOM" {
message = String(data: data, encoding: .utf8) ?? ""
writeData()
} else {
data.append(characteristicData)
}
}
}
I have a simple SwiftUI project with two basic build configurations (Debug, Release) as shown below.
I now choose Build > Scheme > Edit Scheme under Product and select Release as the current build configuration as shown below.
And the Preview canvas exhibit errors.
If I click on the Diagnostics button, it says under PREVIEW UPDATE ERROR
OptimizationLevelError: not building -Onone
”BuildSchemeCrazyDaughter.app” needs -Onone Swift optimization level to use previews (current setting is -O)
What does that mean and why don't I get the preview for the Release build configuration? Thanks.
I have a macOS application developed in SwiftUI. It's a document-based application. I know how to hide the Show Tab Bar command under View. I don't want to hide it. I always want to show tabs. I wonder how to enable this command programmatically such that the document window always has the + button to the right. Thanks.
The other day I was playing with iBeacon and found out that CLBeaconIdentityConstraint will be deprecated after iOS 18.5. So I've written code with BeaconIdentityCondition in reference to this Apple's sample project.
import Foundation
import CoreLocation
let monitorName = "BeaconMonitor"
@MainActor
public class BeaconViewModel: ObservableObject {
private let manager: CLLocationManager
static let shared = BeaconViewModel()
public var monitor: CLMonitor?
@Published var UIRows: [String: [CLMonitor.Event]] = [:]
init() {
self.manager = CLLocationManager()
self.manager.requestWhenInUseAuthorization()
}
func startMonitoringConditions() {
Task {
print("Set up monitor")
monitor = await CLMonitor(monitorName)
await monitor!.add(getBeaconIdentityCondition(), identifier: "TestBeacon")
for identifier in await monitor!.identifiers {
guard let lastEvent = await monitor!.record(for: identifier)?.lastEvent else { continue }
UIRows[identifier] = [lastEvent]
}
for try await event in await monitor!.events {
guard let lastEvent = await monitor!.record(for: event.identifier)?.lastEvent else { continue }
if event.state == lastEvent.state {
continue
}
UIRows[event.identifier] = [event]
UIRows[event.identifier]?.append(lastEvent)
}
}
}
func updateRecords() async {
UIRows = [:]
for identifier in await monitor?.identifiers ?? [] {
guard let lastEvent = await monitor!.record(for: identifier)?.lastEvent else { continue }
UIRows[identifier] = [lastEvent]
}
}
func getBeaconIdentityCondition() -> CLMonitor.BeaconIdentityCondition {
CLMonitor.BeaconIdentityCondition(uuid: UUID(uuidString: "abc")!, major: 123, minor: 789)
}
}
It works except that my sample app can take as long as 90 seconds to see event changes. You would get an instant update with an fashion (CLBeacon and CLBeaconIdentityConstraint). Is there anything that I can do to see changes faster? Thanks.
I've submitted my first AR app for iPhone and iPad to iTunes Connect. After sending a binary to iTunes Connect, I've received the following warning message.
The app contains the following UIRequiredDeviceCapabilities values, which aren’t supported in visionOS: [arkit].
No. 1, my app doesn't support visionOS. No. 2, I don't have the UIRequiredDeviceCapabilities dictionary in info.plist. Why am I receiving this warning? One article related to this issue that I've read suggests that I remove the UIRequiredDeviceCapabilities dictionary. Well, I don't have it in my plist. What can I do with this warning message? Thanks.
Topic:
Spatial Computing
SubTopic:
ARKit
A few weeks ago, I explored the possibility of installing auto-renewal subscription plans on an iOS app. Initially, I thought subscription plans are allowed for news and magazine apps. Then I saw this Apple website, which actually encourages us to provide 'free, limited access to app content.' So I took their advice and submitted an iOS app that has no dynamic content to App Store. I submitted another. Two apps have been approved. And I have got the third one put on hold several days ago. The reviewer has asked me an uncomfortable line of questions repeatedly like
What are changes?
How often?
that I have never received in my 13 or 14 year history. Then he or she rejected it two days ago. And I got a 4th one approved at the same time. So three are admitted in with one rejected.
Does an app have to have dynamic content to use auto-renewal subscription plans? I don't find any statement in Apple Review Guidelines that prohibits me from installing auto-renewal subscription plans on non-dynamic content app. There are other big-time apps like Microsoft 365 and Adobe Photoshop that are available with subscription plans. I am very perplexed.
I've been trying to send an archive with Organizer to iTunes Connect. It's not my first time. I've been doing it for more than a decade. Anyway, when I try to send a package for my new macOS application, Organizer gives me two error messages that I have never seen before.
App Record Creation Error
App Record Creation failed due to an invalid attribute. The SKU you entered has already been used.
App Record Creation Error
App Record Creation failed due to request containing an attribute already in use. The app name you entered is already being used for another app in your account. If you would like to use the name for this app you will need to submit an update to your other app to change the name, or remove it from App Store Connect.
An odd thing is that, as shown in the screenshot below, Organizer demands that I enter an application name and SKU manually. I've entered the exactly same ones from the App Store Connect page. I didn't see this step on Organizer last month.
I'm using a new SKU for this submission. And I don't have an existing application at iTunes Connect with the same application name.
I guess it's the same issue that has been reported here.. I have no pending contract issues.
How do I send an archive without errors? My Xcode version is Version 16.4 (16F6).
Thanks.
I have three toolbar buttons with images from Assets.xcassets. Initially, I didn't use @1x, @2x, @3x sizes. I just put one size (72 x 72) for all of them. It was never a problem till a few days ago.
The reviewer has reported numerous issues, which all seem to originate from miniaturized toolbar images. They have given me a screenshot from an iPad. Now, each of the three to the left has shrunken to 4 x 4, according to them.
Some lines of code are the following.
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationStack {
ZStack {
VStack {
...
...
...
}
.background(.brown)
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(
leading: HStack(content: {
Button {
} label: {
Image("ToolbarImage1")
.resizable()
.foregroundColor(.red)
.aspectRatio(contentMode: .fit)
.frame(width: 28)
}
Button {
} label: {
Image("ToolbarImage2")
.resizable()
.foregroundColor(.cyan)
.aspectRatio(contentMode: .fit)
.frame(width: 28)
}
Button {
} label: {
Image("ToolbarImage3")
.resizable()
.foregroundColor(.gray)
.aspectRatio(contentMode: .fit)
.frame(width: 28)
}
}),
trailing: HStack(content: {
Button {
} label: {
Text("X")
.font(.body)
.fontWeight(.semibold)
.foregroundStyle(colorScheme == .light ? .white : .black)
.frame(width: 28, height: 28)
.background {
Circle()
.fill(!disableGroupMenu ? .green : .green.opacity(0.6))
}
}
Button {
withAnimation(.easeInOut(duration: 0.2)) {
showCopyMenu.toggle()
manageMenu()
}
} label: {
Text("Y")
.font(.body)
.fontWeight(.semibold)
.foregroundStyle(colorScheme == .light ? .white : .black)
.frame(width: 28, height: 28)
.background {
Circle()
.fill(!disableCopyMenu ? .indigo: .indigo.opacity(0.6))
}
}
})
)
.toolbar {
ToolbarItem(placement: .principal) {
Text("App name")
.bold()
.foregroundColor(.white)
}
}
}
}
}
}
I don't see this minituralization issue on any of my actual devices (iPhone XR, iPhone 14, iPad 9th gen.) on top of various simulator models including iPad A16 with iOS 26. This is my first iOS submission after iOS 26 was released. I don't know if it has something to do with iOS 26. The reviewer hasn't told me about their iPad model or the iOS version. I have the same app for macOS, which was submitted after macOS 26 was released. And they haven't reported the miniaturization issue after 4 or 5 software updates.
If you have any idea as to what's causing it, please let me know. I have submitted a new binary with @3x as a resort. I doubt the issue has been resolved. Thanks.
Initally, I've used Xcode 16.4 to built the app. I have tried building it with Xcode 26. And I don't see the minituralization issue on any of the simulator models (iPad mini, iPad A16...).
Topic:
UI Frameworks
SubTopic:
SwiftUI
I nominated my app at the very end of the last year because I felt very good about it for the first time in my 16-year iOS app development history. But an unexpected rejection event has stricken it as depicted in this thread. It was approved 5 days ago. And I'm still furious about rejection because, as usual, they were wrong. Anyway, if I look under Nomination Details, it says that my last submission missed the boat. Is there anything that I can do after it missed the expected publish date?
Topic:
App Store Distribution & Marketing
SubTopic:
App Store Connect