For my app I've created a Dictionary that I want to persist using AppStorage
In order to be able to do this, I added RawRepresentable conformance for my specific type of Dictionary. (see code below)
typealias ScriptPickers = [Language: Bool]
extension ScriptPickers: @retroactive RawRepresentable where Key == Language, Value == Bool {
public init?(rawValue: String) {
guard let data = rawValue.data(using: .utf8),
let result = try? JSONDecoder().decode(ScriptPickers.self, from: data)
else {
return nil
}
self = result
}
public var rawValue: String {
guard let data = try? JSONEncoder().encode(self), // data is Data type
let result = String(data: data, encoding: .utf8) // coerce NSData to String
else {
return "{}" // empty Dictionary represented as String
}
return result
}
}
public enum Language: String, Codable, {
case en = "en"
case fr = "fr"
case ja = "ja"
case ko = "ko"
case hr = "hr"
case de = "de"
}
This all works fine in my app, however trying to run any tests, the build fails with the following:
Conflicting conformance of 'Dictionary<Key, Value>' to protocol 'RawRepresentable'; there cannot be more than one conformance, even with different conditional bounds
But then when I comment out my RawRepresentable implementation, I get the following error when attempting to run tests:
Value of type 'ScriptPickers' (aka 'Dictionary<Language, Bool>') has no member 'rawValue'
I hope Joseph Heller is out there somewhere chuckling at my predicament
any/all ideas greatly appreciated
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
In my code, I do this:
Text("\(languagesManager.availableWords.count)")
And next time I build, this creates an entry in Localizable.strings: %lld
Is there a way I can flag this UI element to indicate its string doesn't need to be localized?
It's related to the passByValue nature of structs. In the sample code below, I'm displaying a list of structs (and I can add instances to my list using Int.random(1..<3) to pick one of two possible predefined versions of the struct).
I also have a detail view that can modify the details of a single struct. However when I run this code, it will instead modify all the instances (ie either Sunday or Monday) in my list.
To see this behaviour, run the following code and:
tap New Trigger enough times that there are multiple of at least one of the sunday/monday triggers
tap one of the matching trigger rows
modify either the day, or the int
expected: only one of the rows will reflect the edit
actual: all the matching instances will be updated.
This suggests to me that my Sunday and Monday static instances are being passed by reference when they get added to the array. But I had thought structs were strictly pass by value. What am I missing?
thanks in advance for any wisdom,
Mike
struct ContentView: View {
@State var fetchTriggers: [FetchTrigger] = []
var body: some View {
NavigationView {
VStack {
Button("New Trigger") {
fetchTriggers.append(Int.random(in: 1..<3) == 1 ? .sunMorning : .monEvening)
}
List($fetchTriggers) { fetchTrigger in
NavigationLink(destination: FetchTriggerDetailView(fetchTrigger: fetchTrigger)
.navigationBarTitle("Back", displayMode: .inline))
{
Text(fetchTrigger.wrappedValue.description)
.padding()
}
}
}
}
}
}
struct FetchTrigger: Identifiable {
static let monEvening: FetchTrigger = .init(dayOfWeek: .monday, hour: 6)
static let sunMorning: FetchTrigger = .init(dayOfWeek: .sunday, hour: 3)
let id = UUID()
enum DayOfWeek: Int, Codable, CaseIterable, Identifiable {
var id: Int { self.rawValue }
case sunday = 1
case monday
case tuesday
var description: String {
switch self {
case .sunday: return "Sunday"
case .monday: return "Monday"
case .tuesday: return "Tuesday"
}
}
}
var dayOfWeek: DayOfWeek
var hour: Int
var description: String {
"\(dayOfWeek.description), \(hour):00"
}
}
struct FetchTriggerDetailView: View {
@Binding var fetchTrigger: FetchTrigger
var body: some View {
HStack {
Picker("", selection: $fetchTrigger.dayOfWeek) {
ForEach(FetchTrigger.DayOfWeek.allCases) { dayOfWeek in
Text(dayOfWeek.description)
.tag(dayOfWeek)
}
}
Picker("", selection: $fetchTrigger.hour) {
ForEach(1...12, id: \.self) { number in
Text("\(number)")
.tag(number)
}
}
}
}
}
When I run this in a playground:
var meDate = Calendar.current.date(from: DateComponents(year: 2024, hour: 7, weekday: 3, weekdayOrdinal: 2))!
print(meDate)
I see:
2024-01-09 15:00:00 +0000
This seems correct to me.
jan 9th is the second Tuesday in 2024
I'm in the pacific TZ, 07:00 PDT matches 15:00GMT
But then I do this:
meDate = Calendar.current.date(bySetting: .weekday, value: 4, of: meDate)!
print(meDate)
and I see: 2024-01-10 08:00:00 +0000
I would have expected my hour value (7PST/15GMT) to have been preserved. Is there a way I can update weekday, but not lose my hour?
At the top level, my app has a tab bar.
the second tab's root view is a list view. When a user taps an item in the list, the app presents a detail view for the list item.
The problem I'm currently having is the behaviour the first time the user taps the second tab's button in the tab bar (when running on an iPad in portrait.
There is a navBar at the top, but the screen is otherwise empty. (tapping the left button on the navBar shows the list view, which allows the user to select an item which populates the main detail view)
Is there some way (in swfitUI) to force the list view to show when in portrait? Alternatively/additionally, is there someway to present some instructional view in the otherwise empty view. (It doesn't appear to be creating a standard detail view here until the user exposes the list and picks an item)
Here is some sample code that demonstrates what I'm describing.
thanks in advance for any assistance!
Mike
import SwiftUI
struct ContentView: View {
var body: some View {
TabView(){
FirstTabView()
.tabItem {
Text("First")
}
.tag(0)
SecondTabView()
.tabItem {
Text("Second")
}
.tag(1)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.previewDevice(PreviewDevice(rawValue: "iPad8,1"))
}
}
struct FirstTabView: View {
var body: some View {
Text("First View")
}
}
struct SecondTabView: View {
var body: some View {
NavigationView {
NavigationLink(destination: Text("Detail View")) {
Text("SummaryView")
}
.navigationBarTitle("Navigation")
}
}
}
I have a SwiftUI app with a List displaying an array of model objects. When the user taps a list item we see its detail view.
I want to add previous and next buttons to my detail view, but I'm not sure what needs to happen when previous/next are tapped. (see code below for what I'm looking to do)
My first thought is to make the model variable in the DetailView be a binding, but I'm not sure how this would tie in with the NavigationLink 'stuff'
any/all suggestions appreciated.
thanks!
class Model: Identifiable {
var modelValue: Int
init(modelValue: Int) {
self.modelValue = modelValue
}
static let testData = [Model(modelValue: 3), Model(modelValue: 7), Model(modelValue: 31)]
}
class ModelManager {
static let shared = ModelManager()
let modelList = Model.testData
func previous(for model: Model) - Model? {
if let index = modelList.firstIndex(where: {$0.modelValue == model.modelValue}) {
if index 0 {
return modelList[index - 1]
}
}
return nil
}
func next(for model: Model) - Model? {
if let index = modelList.firstIndex(where: {$0.modelValue == model.modelValue}) {
if index modelList.count - 1 {
return modelList[index + 1]
}
}
return nil
}
}
struct ContentView: View {
let manager:ModelManager = ModelManager.shared
var body: some View {
NavigationView {
List(manager.modelList) { object in
NavigationLink(
destination: DetailView(model: object, previous: manager.previous(for: object), next: manager.next(for: object)),
label: {
Text("fred \(object.modelValue)")
})
}
}
}
}
struct DetailView: View {
var model: Model
var previous: Model?
var next: Model?
var body: some View {
VStack {
HStack {
if previous != nil {
Button("Previous") {
// goto previous
}
}
Spacer()
if next != nil {
Button("Next") {
// goto next
}
}
}
Text("value: \(model.modelValue)")
Spacer()
}
}
}
This will be the initial production schema for this container.
When I attempt to start deployment, the Confirm Deployment dialog appears and spins for a while. It then reports "There was a problem loading the environment's status."
When I clear the error the Confirm Deployment dialog reports: "No Changes to Deploy"
"The schema in the development environment is the same as production."
(spoiler, they are not the same)
Any suggestions?
When I:
open an existing project
create a new PhotoExtensions target
run the new target in an iOS simulator (eg iPhone 15, iOS 17.0)
Select photos as the app to run
Open a photo
Tap the ... button at the top right
I see: Copy, Duplicate, Hide, etc.
But I do not see my new Extension. Is there something else I need to be doing in order to see my new Extension in 'action'?
It feels like this should be easy, but I'm having conceptual problems about how to do this. Any help would be appreciated.
I have a sample app below that works exactly as expected. I'm able to use the Slider and Stepper to generate inputs to a function that uses CoreImage filters to manipulate my input image. This all works as expected, but it's doing some O(n) CI work on the main thread, and I want to move it to a background thread. I'm pretty sure this can be done using combine, here is the pseudo code I imagine would work for me:
func doStuff() {
// subscribe to options changes
// .receive on background thread
// .debounce
// .map { model.inputImage.combine(options: $0)
// return an object on the main thread.
// update an @Published property?
}
Below is the POC code for my project. Any guidance as to where I should use combine to do this would be greatly appreciate. (Also, please let me know if you think combine is not the best way to tackle this. I'd be open to alternative implementations.)
struct ContentView: View {
@State private var options = CombineOptions.basic
@ObservedObject var model = Model()
var body: some View {
VStack {
Image(uiImage: enhancedImage)
.resizable()
.aspectRatio(contentMode: .fit)
Slider(value: $options.scale)
Stepper(value: $options.numberOfImages, label:
{
Text("\(options.numberOfImages)")})
}
}
private var enhancedImage: UIImage {
return model.inputImage.combine(options: options)
}
}
class Model: ObservableObject {
let inputImage: UIImage = UIImage.init(named: "IMG_4097")!
}
struct CombineOptions: Codable, Equatable {
static let basic: CombineOptions = .init(scale: 0.3, numberOfImages: 10)
var scale: Double
var numberOfImages: Int
}
I'm defining a typealias for a set, and then creating an extension for the new typealias.
When I do this, I'm getting an odd syntax error. Any/all guidance appreciated.
typealias IntSet = Set<Int>
extension IntSet {
func aFunction() -> Set<String> {
let array: [String] = self.map { "\($0)" }
return Set(array)
}
}
At the return line, I get the following syntax error: Cannot convert return expression of type 'Set<Int>' to return type 'Set<String>'
Even if I replace the return line with the following, I get the same compile error
return Set("array")
I have a singleton instance of a class that (among other things) is managing which subset of words will be available to users.
The contents of availableWords will always be a subset of words and is always a function of three userDefaults that are bound to user settings (using @AppStorage)
I could dynamically reconstruct availableWords every time it is needed, but it will be read much more frequently than it changes. Because of this, I want to cache the updated list every time a user changes one of the settings that will change its contents.
But the only way I can see to do this is to create an update function and rely on the UI code to call the function any time a user updates one of the settings that will require availableWords to be updated. And this feels more like something out of UIKit.
Do any of you see a better way of managing the updates of availableWords?
class WordsManager {
static let shared = WordsManager()
let words: Words // defined in init
var availableWords: Words // updated anytime scriptPickers, languageChoice or cardPile changes
@AppStorage("scriptPickers") var scriptPickers: ScriptPickers = ScriptPickers.defaultDictionary
@AppStorage("languageChoice") var languageChoice: LanguageChoice = .all
@AppStorage("cardPile") var cardPile: CardPile = .allRandom
func updateAvailableWords() {
var result = words.filtered(by: cardPile.wordList)
let askIdentifiers = languageChoice.askLanguages
let answerIdentifiers = languageChoice.answerLanguages
result = result.matching(askIdentifiers: askIdentifiers, answerIdentifiers: answerIdentifiers)
self.availableWords = result
}
// other stuff
}
I've created a UserDefaults extension to generate custom bindings.
extension UserDefaults {
func boolBinding(for defaultsKey: String) -> Binding<Bool> {
return Binding (
get: { return self.bool(forKey: defaultsKey) },
set: { newValue in
self.setValue(newValue, forKey: defaultsKey)
})
}
func cardPileBinding(for defaultsKey: String) -> Binding<CardPile> {
return Binding (
get: { let rawValue = self.object(forKey: defaultsKey) as? String ?? ""
return CardPile(rawValue: rawValue) ?? .allRandom
},
set: { newValue in
self.setValue(newValue.rawValue, forKey: defaultsKey)
})
}
}
For the sake of completeness, here is my enum
enum CardPile: String, CaseIterable {
case allRandom
case numbers
case numbersRandom
case daysMonths
case daysMonthsRandom
}
I've also created UI elements that use these bindings:
var body: some View {
VStack {
Toggle("Enable", isOn: UserDefaults.standard.boolBinding(for: "enable"))
Picker("Card Pile", selection: UserDefaults.standard.cardPileBinding(for: "cardPile")) {
ForEach(CardPile.allCases,
id: \.self) {
Text("\($0.rawValue)")
.tag($0.rawValue)
}
}
}
}
When I tap the toggle, it updates correctly. However when I tap the picker and select a different value, the binding setter gets called, but the view does not refreshed to reflect the change in value. (If I force quit the app and re-run it, the I see the change.)
I would like to find out why the Binding works as I'd expected (ie updates the UI when the value changes) but the Binding behaves differently.
any/all guidance very much appreciated.
Note: I get the same behavior when the enum use Int as its rawValue
is now broken. (but definitely worked when I originally wrote my Document-based app)
It's been a few years.
DocumentBrowserViewController's delegate implements the following func.
func documentBrowser(_ controller: UIDocumentBrowserViewController, didRequestDocumentCreationWithHandler importHandler: @escaping (URL?, UIDocumentBrowserViewController.ImportMode) -> Void) {
let newDocumentURL: URL? = Bundle.main.url(forResource: "blankFile", withExtension: "trtl2")
// Make sure the importHandler is always called, even if the user cancels the creation request.
if newDocumentURL != nil {
importHandler(newDocumentURL, .copy)
} else {
importHandler(nil, .none)
}
}
When I tap the + in the DocumentBrowserView, the above delegate func is called (my breakpoint gets hit and I can step through the code) newDocumentURL is getting defined successfully and
importHandler(newDocumentURL, .copy)
gets called, but returns the following error:
Optional(Error Domain=com.apple.DocumentManager Code=2 "No location available to save “blankFile.trtl2”." UserInfo={NSLocalizedDescription=No location available to save “blankFile.trtl2”., NSLocalizedRecoverySuggestion=Enable at least one location to be able to save documents.})
This feels like something new I need to set up in the plist, but so far haven't been able to discover what it is.
perhaps I need to update something in info.plist? perhaps one of:
CFBundleDocumentTypes
UTExportedTypeDeclarations
Any guidance appreciated.
thanks :-)
I have a view containing either a TextField or a SecureField. I'm hoping I can use a single FocusState value that will apply to either the TextField or SecureField. (I'm using FocusState to ensure the cursor will be in the field when it initially loads)
I've verified this works in a sample project when the view is in a WindowGroup. But when I instead use a DocumentGroup ~50% of the time when the view loads/launches, it does not have focus.
Here is my ContentView:
struct ContentView: View {
let coinFlip: Bool = .random() // used to choose between the TextField and the SecureField
@State var fieldContent: String = "" // bound to the Field value
@FocusState var focus: Bool
var body: some View {
VStack {
Text("Coin Flip: \(coinFlip)")
actualField
.focused($focus, equals: true)
}
.onAppear() {
focus = true
}
}
@ViewBuilder var actualField: some View {
if coinFlip {
TextField("Enter text here", text: $fieldContent)
} else {
SecureField("Enter secure text here", text: $fieldContent)
}
}
}
and here is my App swift file
@main
struct ModernTurtleApp: App {
var body: some Scene {
// WindowGroup {
// ContentView()
// }
DocumentGroup(newDocument: ModernTurtleDocument()) { file in
ContentView()
}
}
}
When this code runs, the Field has focus about 50% of the time on initial launch. When I uncomment the WindowGroup and comment the DocumentGroup, the field always has focus on initial launch.
I realize I can work around this behaviour by using an optional enum for FocusState. I would be ok going this route, but I first want to try to understand the behaviour I'm seeing, and possibly keep my simpler FocusState implementation.
Thanks, in advance for any help.
Sorry if this question is too vague, however I've tried this multiple times and see the same result. I'm pretty sure I'm doing something wrong, but don't know what.
I have a Multiplatform (iOS and macOS) project that builds, and runs
I add a new target of type Unit Test Bundle
I click the diamond in the margin beside the XCTestCase declaration at the top of the new Test file
The target project builds, then Xcode says 'Testing...' and it stays like this forever.
I've also tried creating a new scheme that targets the target created in the above steps. attempting to run my tests behaves the same. The top status bar will get stuck saying 'Testing...' and never get anywhere.
I'm pretty sure this is something basic.
thanks, in advance for any guidance.
Mike