Post not yet marked as solved
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
}
In V 0.1 of my app, I went with a simple option for a piece of business logic in my app.
I then changed something else that caused my simplified business logic to now crash the app at startup.
Excellent, a chance to use Test. Driven Design to replace my flawed business logic.
So I wrote my first test TDD test, but when I tried to run the test... It doesn't run because running the test target seems to start by actually running my full app (which as I described in chapter 1 is currently crashing)
Have I configured something incorrectly? or is it normal that when I attempt to run a single test, step 1 is running the full app?
(note: it is very easy for me to disable/avoid the business logic causing my crash. This question is more about my lack of understanding of what it means to run a test target.)
thanks in advance for any and all responses.
Mike
Post not yet marked as solved
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")
Post not yet marked as solved
I am using @AppStorage in a model object (see code below that works as expected).
class ModelObject {
static let shared = ModelObject()
@AppStorage("enhanced") var scriptPickers: Bool = true
var defaultDependentValue: String {
scriptPickers ? "Enhanced" : "NOT enhanced"
}
}
struct ContentView: View {
@AppStorage("enhanced") var scriptPickers: Bool = true
var body: some View {
VStack {
Toggle(isOn: $scriptPickers, label: {
Text("userDefault val")
})
Text("value: \(ModelObject.shared.defaultDependentValue)")
}
}
}
Now I want to test my model object in a way that will allow me to use a mock instance of UserDefaults, but am having trouble with the syntax. I tried adding a userDefaults var, and referring to the var in the @AppStorage
class ModelObject {
static let shared = ModelObject()
let userDefaults: UserDefaults
init(userDefaults: UserDefaults = .standard) {
self.userDefaults = userDefaults
}
@AppStorage("enhanced", store: userDefaults) var scriptPickers: Bool = true
var defaultDependentValue: String {
scriptPickers ? "Enhanced" : "NOT enhanced"
}
}
However I can't find a way to avoid the syntax error this generates:
Cannot use instance member 'userDefaults' within property initializer; property initializers run before 'self' is available
Any guidance on how I might be able to:
continue using @AppStorage
be able to test my class in a way that doesn't force me to use UserDefaults.standard
thanks, in advance,
Mike
Post not yet marked as solved
This first code works fine decoding json.
static let localizationsDictText = """
{ "en" : { "string" : "Cat" },
"fr" : { "string" : "Chat"}
}
"""
struct Localization: Codable, Equatable {
let string: String
}
typealias LocalizationsDict = [String: Localization]
func testExample() throws {
let text = Self.localizationsDictText
let data = text.data(using: .utf8, allowLossyConversion: false)
let localizationsDict = try JSONDecoder().decode(LocalizationsDict.self, from: data!)
XCTAssertEqual(localizationsDict.keys.count, 2)
}
But then I try to create a modified version of the above, only changing the type of the LocalizationsDict key from String to an enum:
enum Language: String, CaseIterable, Codable {
case en = "en"
case fr = "fr"
}
typealias LocalizationsDict2 = [Language: Localization]
func testExample2() throws {
let text = Self.localizationsDictText
let data = text.data(using: .utf8, allowLossyConversion: false)
let localizationsDict = try JSONDecoder().decode(LocalizationsDict2.self, from: data!)
XCTAssertEqual(localizationsDict.keys.count, 2)
}
and the JSONDecoder line throws an exception:
testExample2(): failed: caught error: "typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array<Any> but found a dictionary instead.", underlyingError: nil))"
Any suggestions as to why switching from
[String: Localization] to [Language: Localization] might cause JSONDecode() to treat it like an array?
Thanks in advance for any guidance.
Post not yet marked as solved
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
}
Post not yet marked as solved
I have a SwiftUI view that works as expected in a full SwiftUI context. But I now need to use it in a UIViewController. It mostly works, but I'm trying to expose an @State var out to the viewController, and it only ever returns the initial value.
Any guidance for how best to pass out this @State var?
I could make it be a binding, but in my pure SwiftUI code, it works fine as @State (ie EditorView's container view does not need to know about sliderVal)
thanks, in advance, for any suggestions
import SwiftUI
import UIKit
class ViewController: UIViewController {
var host: UIHostingController<EditorView>?
override func viewDidLoad() {
super.viewDidLoad()
host = .init(rootView: EditorView())
guard let host = host else { return }
addChild(host)
host.view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(host.view)
host.didMove(toParent: self)
}
@IBAction func helloTapped(sender: UIButton) {
guard let sliderValue = host?.rootView.sliderVal else { return }
print("UIKit sliderValue: \(sliderValue)")
}
}
struct EditorView: View {
@State var sliderVal: Double
init(sliderVal: Double? = nil) {
_sliderVal = State(initialValue: sliderVal ?? 7)
}
var body: some View {
VStack {
Slider(value: $sliderVal, in: 1...10)
Text("sliderVal: \(sliderVal)")
}
}
}
(NOTE: in order to see this code snippet in action you will need to create a button in the storyboard and link it to helloTapped)
Post not yet marked as solved
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'?
Post not yet marked as solved
When I:
open a simulator (eg iPhone 15, iOS 17.0)
open the Photos app
tap on an image (either one that comes included, or something I've saved to PhotoRoll)
tap Edit
I see the typical Photos Edit view with two key differences.
the image is not visible, the middle of the view is all black
there are now Cancel/Done/Dismiss buttons. I need to force quit to get back to the Springboard (Is that still the correct term?)
Am I doing something wrong, or should the simulator's Photo's app be providing a better edit experience?
I'm trying to find a syntactically correct way to put the contents of a Container in a separate variable (or function).
Can anyone steer me in the right direction?
thanks, in advance,
mike
struct ContentView: View {
var body: some View {
VStack(content: containerContent)
.padding()
}
var containerContent: () -> Content {
return {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
}
}
}
I'm getting this error when attempting to upload an iOS app to iTunes Connect. I've tried using Transporter, and get the same result.I've also tried uploading a new version of an app that uploaded correctly in November and got the same result.Does anyone have any suggestions for possible causes for this error?thanks,
Post not yet marked as solved
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")
}
}
}
Post not yet marked as solved
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?
Post not yet marked as solved
See sample code below...
Basically it's a galleryView with a dataSource that can add/remove items dynamically. It works as expected when GalleryView's dataSource variable has a type (that conforms to ObservableObject)
However when I change dataSource's type to be a protocol, I can't seem to get my code to compile.
Any guidance on how to use a protocol in GalleryView, and continue to keep the UI updating when the model object's item list changes?
thanks!
Mike
protocol GalleryDataSource: ObservableObject {
var itemCount: Int { get }
func item(for index: Int) - String
}
class GalleryModel: ObservableObject {
static let test1: GalleryModel = GalleryModel(items: ["A","B","C"])
@Published var items: [String]
init(items: [String]) {
self.items = items
}
}
extension GalleryModel: GalleryDataSource {
var itemCount: Int {
return items.count
}
func item(for index: Int) - String {
return items[index]
}
}
struct ContentView: View {
var model: GalleryModel = GalleryModel.test1
var body: some View {
VStack {
GalleryView(dataSource: model)
Button("Add Item") {
model.items.append("\(model.items.count)")
}
}
}
}
struct GalleryView: View {
@ObservedObject var dataSource: GalleryModel //GalleryDataSource
var body: some View {
ScrollView(.horizontal, content: {
HStack {
ForEach(0..self.dataSource.itemCount, id:\.self) { index in
Text(dataSource.item(for: index))
.padding()
}
}
})
}
}
Post not yet marked as solved
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()
}
}
}