Post

Replies

Boosts

Views

Activity

Reply to Deleting an app fro the App Store so I can use newer version of the same app
I sent a 1.0 version of my app through review but decided it wasn't ready so I rejected it. Now I've upload 1.1 which shows in TestFlight and distributes to testers okay but I don't see it in my apps listing. I wonder why not. And will it inherit all the stuff I put into the 1.0, inclusing screenshots, or do I need to start that over again. I thought 1.1 would just continue from 1.0. How is this supposed to work (this is my first app)?
Sep ’24
Reply to Unable to open a PDF document from with my app
As an update, I tried to use the Link like a http url and am reserving my FileLinkView for images only (for now, as a test), hoping the Link view was clever enough to deal with both http and file url schemes. Here's that code... struct InstructionLinksView: View { let links: [InstructionLink] var body: some View { ForEach(links, id: \.url) { link in if link.url.hasPrefix("http") { Link(destination: URL(string: link.url)!, label: { Text(link.label) .foregroundColor(Color(.systemBlue)) }) } else if link.url.hasPrefix("file") && link.url.hasSuffix("pdf") { Link(destination: URL(string: link.url)!, label: { Text(link.label) .foregroundColor(Color(.systemBlue)) }) } else if link.url.hasPrefix("file") { FileLinkView(link: link) } } } } No joy. I still get no PDF document. I'm missing something. Thanks for your help!
Feb ’24
Reply to Subclass not decoding through superclass
I believe I found a solution to this. The error I was getting was caused by getting the container for the JSON data too many times, using up the data out of step of my intent. I guess it has something to to with how data is streamed from the file into the decoder. The way out is ... To break encapsulation a bit by making the CheckTask super class aware of all keys of of any of it's subclasses so that when the decoding process gets a nestedContainer, it's ready to go with all key knowledge whether it uses them all or not. that means the same container can be used for the decoding of any subclass. and Each subclass implements an extra init function that takes that container as an argument, then specifically decodes from there. Here are some pertinent code snippets... class CheckList: Identifiable, ObservableObject, Codable { let id: String var title: String @Published var tasks: [CheckTask] var domain: String .... required init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decode(String.self, forKey: .id) title = try container.decode(String.self, forKey: .title) domain = try container.decode(String.self, forKey: .domain) // get the CheckTasks even though we don't know what each one is yet var tasksContainer = try container.nestedUnkeyedContainer(forKey: .tasks) tasks = [] // Iterate through the array of objects, determine the type, and decode accordingly while !tasksContainer.isAtEnd { // Because we don't know which subclass yet, we toss all possible keys at the taskContainer. // We only get one bite at the apple and it has to be prepared to decode anything. // See CheckTask.CodingKeys for more info. let taskContainer = try tasksContainer.nestedContainer(keyedBy: CheckTask.CodingKeys.self) let type = try taskContainer.decode(CheckType.self, forKey: .type) // Now that we know the type, we choose how to init the correct class, // using a convenience method that takes a container, // then append to the array of tasks. switch type { case .CheckItem: let item = try CheckItem(container: taskContainer) tasks.append(item) default: // Handle unknown type or log a warning print("Unknown task type:.") } } } } class CheckTask: Identifiable, ObservableObject, Equatable, Codable { var id: UUID var locked: Bool var type: CheckType .... // CodingKeys to specify custom mapping between Swift names and JSON keys // I hate to break encapsulation like this but, hey, that's decoding for ya. enum CodingKeys: String, CodingKey { // common keys case id, locked, type // CheckItem keys case title, checkmark, skippable, complete, instructions, instructionLinks // CheckGroup keys case items } required init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decode(UUID.self, forKey: .id) locked = try container.decode(Bool.self, forKey: .locked) type = try container.decode(CheckType.self, forKey: .type) } } enum CheckType: Codable { case CheckItem case CheckGroup case Unknown } class CheckItem: CheckTask { var title: String @Published var checkmark: CheckMark var skippable: Bool = false var instructions: String var instructionLinks: [InstructionLink] .... // Initialize with values init(id: UUID, title: String, checkmark: CheckMark, skippable: Bool, locked: Bool, instructions: String?, instructionLinks: [InstructionLink]?) { self.title = title self.checkmark = checkmark self.skippable = skippable self.instructions = instructions ?? "" self.instructionLinks = instructionLinks ?? [] super.init(id: id, locked: locked, type: CheckType.CheckItem) } /// Custom decoding method to decode the properties from a JSON container. This is used by the CheckList decode /// so that it can handle instantiating subclasses of CheckTask blindly since it doesn't know there are subclasses /// and can't direct specify a type. /// - Parameter container: <#container description#> convenience init(container: KeyedDecodingContainer<CodingKeys>) throws { self.init(id: try container.decode(UUID.self, forKey: .id), title: try container.decode(String.self, forKey: .title), checkmark: try container.decode(CheckMark.self, forKey: .checkmark), skippable: try container.decode(Bool.self, forKey: .skippable), locked: try container.decode(Bool.self, forKey: .locked), instructions: try container.decode(String.self, forKey: .instructions), instructionLinks: try container.decode([InstructionLink].self, forKey: .instructionLinks) ) } } So now I'll implement another subclass of CheckTask called CheckGroup and see how I can break stuff then.
Jan ’24
Reply to SwiftUi view responding to model changes
This has uncovered another issue that I think is related somehow. The CheckItemCardView works fine on it's own but when it is in a list of card views, pressing one of the card buttons acts like all of the card's buttons are activated, making for strange behavior. below are the two views I'm talking about. import SwiftUI struct CheckListView: View { @ObservedObject var checkList: CheckList var body: some View { List { ForEach(checkList.items.indices, id: \.self) { index in CheckItemCardView(checkItem: checkList.items[index], index: index) } } } } struct CheckListView_Previews: PreviewProvider { static var checkList = PreviewData.makeCheckList() static var previews: some View { CheckListView(checkList: checkList ) } } import SwiftUI struct CheckItemCardView: View { @ObservedObject var checkItem: CheckItem let index: Int var completed: Bool = false @State var isPresentingInstructionView = false var body: some View { HStack { Button { switch checkItem.checkmark { case .undone: checkItem.setDone() print("undone toggled to ", checkItem.checkmark) case .done: checkItem.setUndone() print("done toggled to ", checkItem.checkmark) default: checkItem.setDone() print("unsure toggled to ", checkItem.checkmark) } } label: { switch checkItem.checkmark { case .undone: Image(systemName: "square") case .done: Image(systemName: "checkmark.square") case .unsure: Image(systemName: "square") .background(Color(UIColor.yellow)) .foregroundColor(Color(.red)) default: Image(systemName: "square").opacity(0.0) } } .onReceive(checkItem.$checkmark) { newCheckmark in print("Checkmark is \(checkItem.checkmark) and new one \(newCheckmark) in card \(index)") } Text(checkItem.title) .strikethrough(checkItem.isSkipped()) Spacer() HStack (alignment: .bottom) { Button { isPresentingInstructionView = true print("isPresentingInstructionView set to true") } label: { Label("how-to", systemImage: "questionmark.bubble") } .labelStyle(VerticalLabelStyle()) .hidden(checkItem.noInstructions()) Button { if (!checkItem.isSkipped()) { checkItem.setSkipped() print("skipping, checkmark set to ", checkItem.checkmark) } else if (checkItem.isSkipped()) { checkItem.setUndone() print("unskipping, checkmark set to ", checkItem.checkmark) } } label: { if (!checkItem.isSkipped()) { Label("skip", systemImage: "arrowshape.bounce.right") } else if (checkItem.isSkipped()) { Label("unskip", systemImage: "arrowtriangle.backward") } } .labelStyle(VerticalLabelStyle()) .hidden(!checkItem.skippable) } .foregroundColor(Color(.systemBlue)) } .font(.title) .sheet(isPresented: $isPresentingInstructionView) { VStack { ScrollView { Text(checkItem.instructions) .padding() Section(header: Text("External Links") .font(.title3) ) { ForEach(checkItem.instructionLinks.sorted(by: >), id: \.key) { key, value in Link(destination: URL(string: key)!, label: { Text(value) }) } } } .frame(maxWidth: .infinity, maxHeight: .infinity) Button("Done", systemImage: "circle.inset.filled") { isPresentingInstructionView = false print("I read your dumb instructions") } .font(.title2) } } } } // https://medium.com/macoclock/make-more-with-swiftuis-label-94ef56924a9d struct VerticalLabelStyle: LabelStyle { func makeBody(configuration: Configuration) -> some View { VStack(alignment: .center, spacing: 2) { configuration.icon configuration.title.font(.callout) } } } extension View { func hidden(_ shouldHide: Bool) -> some View { opacity(shouldHide ? 0 : 1) } } struct CheckItemCardView_Previews: PreviewProvider { static var checkItem = PreviewData.makeLoneItem() static var previews: some View { CheckItemCardView(checkItem: checkItem, index: 1 ) } } so when I click on the first button, it also opens up the sheet and sets the checkmark value to "skipped", not at all what I want. And, again, this doesn't happen if I run just one CheckItemCardView all by itself. Can you explain why the list would make one button press activate all buttons in the card? Thanks in advance.
Jan ’24
Reply to Completion Handler Skipped
Quinn! Thank you! I wasn't aware that XCTest had a different lifecycle so thank you for opening my eyes. As a follow up question, if expectations or concurrency are not needed in the 'real' app, are they still considered best practice? Thank you for getting me out of this jam.
Jun ’23