Post

Replies

Boosts

Views

Activity

Reply to How to manage manual ordering of entities in Core Data?
hi,a possible solution is to create a "master" Core Data entity whose only attribute is a one-to-many relationship to the entity that maintains data associated with a cell of the CollectionView.the ordered array of such an entity would come across in code generation of type NSOrderedSet?. assume, for the moment, that images are loaded to the CollectionView, perhaps via entities named "ImageDataCD", so this "master" Core Data entity would havevar images: NSOrderedSet?if you set Codegen to Class Definition, you'll see that you then get a whole slew of about 10 accessors to work with the images variable, which include: @objc(insertObject:inImagesAtIndex:) @NSManaged public func insertIntoImages(_ value: ImageDataCD, at idx: Int) @objc(removeObjectFromImagesAtIndex:) @NSManaged public func removeFromImages(at idx: Int) @objc(replaceImagesAtIndexes:withImages:) @NSManaged public func replaceImages(at indexes: NSIndexSet, with values: [ImageDataCD]) @objc(addImagesObject:) @NSManaged public func addToImages(_ value: ImageDataCD)now you can load up this "master" in the order you want (use addToImages()) when it is created. as the order is changed in the CollectionView, do the appropriate reshuffling (use some combination of removeFromImages(), insertIntoImages(: at), and replaceImages(at: with:) and save the context.it sounds workable, anyway ... perhaps this is what you were looking for?hope that helps,DMG
Feb ’20
Reply to NavigationView extra sapce
hi,you said "If I did that I couldn’t ..."you didn't say "I did that and it doesn't ... and here's my updated code ..."you might look back at this related thread of a few days ago: https://forums.developer.apple.com/thread/128454EDIT ADDED 4 DAYS AFTER THIS RESPONSE: i hope you have not chosen to not continue because my answer might have been construed as off-putting or dismissive. we really do like to help people in this forum, but we really also do like to have code in front of us to work with. nevertheless, i'll repeat my initial response, in case it was not clear what i meant by "remove the navigation wrapper." i still claim that if you comment out lines 32 and 37 in your original code, things will work as (i think) you intended.hope that helps,DMG
Feb ’20
Reply to Two Back headers after NavigationLink
hi,this is the code i'm running, using XCode 11.3.1 and with the simulator running iOS 13.3struct ContentView: View { var body: some View { NavigationView{ ScrollView{ VStack{ NavigationLink(destination: ListView1()){ Text("Tap me") } Text("Nothing here") } }.navigationBarTitle("Main Level") // added } } } struct ListView1: View { var body: some View { List{ NavigationLink(destination: DetailView1()){ Text("Tap me one more time") } Text("Item 2") Text("Item 3") }.navigationBarTitle("Level 2", displayMode: .inline) // added } } struct DetailView1: View { var body: some View { // NavigationView{ // remove NavigationView wrapper here ScrollView() { VStack{ NavigationLink(destination: DetailView2()){ Text("Drill down more") } Text("Nothing here") } }.navigationBarTitle("Level 3", displayMode: .inline) // added // } } } struct DetailView2: View { var body: some View { List { Text("That's it") Text("Nothing here") }.navigationBarTitle("Level 4", displayMode: .inline) // added } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }as far as i can see, this is working fine without two navigation bars (i believe as you intended). uncomment the NavigationView wrapper at lines 34/43 and you have the double navigation bars.hope that helps,DMG
Feb ’20
Reply to Two Back headers after NavigationLink
hi,seems to work fine for me if you remove the NavigationView wrapper on the ScrollView in DetailView1 (at line 44).it might help debugging this if you first add .navigationBarTitle("Main Level") to the ScrollView in the ContentView, and then also .navigationBarTitle("Level X", displayMode: .inline) in each of ListView1 ("X" = "1"), DetailView1 ("X" = "2"), and DetailView2 ("X" = "3"). then you'll see the problem.hope that helps,DMG
Feb ’20
Reply to For loop question
hi,glad to hear you've figured this out, and yes, it will be slow with lots of jokers. there are just a lot of loops to run and a lot of possibilities to check, although those loops are hidden in the recursion rather than writing them directly.for what it's worth, i did hack out something in Obj-C that somewhat mirrors what i wrote earlier in Swift -- and i also added in a check to be sure to not duplicate any regular card (integer in 1...52) in the process. this is what i got (it's very clunky):#import <Foundation/Foundation.h> // this is the function that should evaluate a hand that has no jokers // the value should be non-negative, with higher return values meaning // better hands int nonJokerValue(NSArray* handOfCards) { return 1; } int valueHelper(NSArray* part1, NSArray* part2) { // if part2 is empty, then part1 is all non-jokers, so evaluate it here. // for now, print it (to see we get all possibilities) and return // its nonJokerValue if ([part2 count] == 0) { NSLog(@"%@", part1); return nonJokerValue(part1); } // otherwise, get first element of part2 NSObject* firstObj = [part2 firstObject]; // form a new part2 with the first object removed NSMutableArray* newPart2 = [[NSMutableArray alloc] initWithArray: part2]; [newPart2 removeObjectAtIndex:0]; // what we do now depends on the first card in part2. if it's a regular card // in the deck (a number 1...52), just append it to the newFirstPart // and make the recursive call if (![firstObj isEqual: @53]) { // get a mutable version of the first part NSMutableArray* newPart1 = [[NSMutableArray alloc] initWithArray: part1]; [newPart1 addObject: firstObj]; return valueHelper(newPart1,newPart2); } // no look through all possible substitutions for the joker moving forward // with a regular card and and use the largest value ever seen int maxValue = 0; for (int i = 1; i <= 52; i++) { // copy the first part NSMutableArray* newPart1 = [[NSMutableArray alloc] initWithArray: part1]; // skip over the case of repeating a regular card that's already there if ([newPart1 containsObject: [NSNumber numberWithInt: i]]) { continue; } // append next number to try with the first part [newPart1 addObject: [NSNumber numberWithInt: i]]; // get the value for this new part1/part2 pair, and update // maxValue if it's larger int v = valueHelper(newPart1,newPart2); if (v>maxValue) { maxValue = v; } } return maxValue; } // main entry point to evaluating a hand of cards int value(NSArray* someArray) { return valueHelper(@[], someArray); } int main(int argc, const char * argv[]) { @autoreleasepool { NSLog(@"%i", value(@[@2, @3, @53, @5, @53])); } return 0; }feel free to use or ignore, as you wish.good luck,DMG
Feb ’20
Reply to For loop question
hi,please let me apologize for posting Swift code. everything i read in the thread was either pseudo-code or Swift, and i failed to read the last three words of your original post, which were "in obj c?"also, i have moved this response out to the main level of the thread, just so we don't keep indenting everyone else's reply from here on.on language that i have used: prefix and suffix are simply terms to denote the first part of an array and the second part of an array. example: [2,3,4,5,6] might be split as a prefix of [2,3,4] and a suffix of [5,6]. if so, the trick is to move the leading 5 from the suffix into the prefix and recursively consider prefix [2,3,4,5] and suffix [6] ... and then as prefix [2,3,4,5,6] and suffix []. when a joker is the lead item of a suffix, that's when one must loop over what to append to the prefix when replacing removing the joker at from the start of the suffix.it may take me awhile to come up with an Obj-C equivalent -- i haven't written any Obj-C since Swift came out; NSArray and NSMutableArray are different critters; and even remembering how to carry out the simplest of operations is rusty. and i keep getting these error messages "Expected ';' after expression" !if anyone else wants to jump in before i come back to this, please do! apart from massaging the code and syntax to handle extracting the first item from an NSMutableArray and appending it to an NSMutableArray, the algorithm does work.i hope to have more later, if i can get up the courage to try some Obj-C !DMG
Feb ’20
Reply to For loop question
PBK and edgeoftheatlas,PBK is right, although moving the pseudo-code into Swift will be challenging until you get the right point of view about how recursion works. additionally, PBK's phrase "loop through each card in the hand" can be absorbed directly in the recursion.i'd describe the recursion this way: for a hand of cards that is represented as say x = [3,2,53,4,53], with real cards being 1...52 and a joker as 53, it helps to think of finding the value of score or the value of the hand by breaking it out is "prefix" and "suffix" terms:the value of [3,2,53,4,53]is the value of [] and [3,2,53,4,53] joinedis the value of [3] and [2,53,4,53] joinedis the value of [3,2] and [53,4,53] joinedis the maximum value of [3,2,x] and [4,53] joined, for each possible xis the value of [3,2,x,4] and [53] joinedis the maximum value of [3,2,x,4,y] and [] joined, for each possible yis the "no jokers" value of [3,2,x,4,y]when you look at it like this, i suggest a simple recursion, but it requires a little subtlety to get things started-- a function that i call "noJokersValue" that returns the value of any hand that has NO jokers.-- a function that i call "value" that returns the value of any hand (jokers or not, as many jokers as you like, for a hand of any length) by kicking off a recursive call to a function that figures out the value of a hand with prefix [] and suffix [3,2,53,4,53]-- this recursive function when called then looks at the suffix.(1) if the suffix is empty, evaluate the "no jokers" value of the prefix (it has no jokers)(2) if the suffix is not empty, identify and remove the first card in the suffix(2a) if the card is not a joker, add it to the prefix and recurse with the remaining suffix(2b) if the card is a joker, loop over all possible substitutions for the card, adding each to the prefix and recursing with the remaining suffix. pick the biggest of all 52 of these.so i have the following code for your consideration. it evaluates every hand to have a value of "1," but prints the hand so that you can see you're getting all possible combinations. the code also does not depend on the length of the array.import Foundation // assume cards are numbered 1...52 as you suggested // use 53 to represent a Joker let kJoker = 53 // this first function assigns a value to every hand that has no jokers. // for this demonstration, we assume that the value of any hand will // be non-negative. func noJokersValue(hand: ArraySlice<Int>) -> Int { print(hand) return 1 } // the value function assigns a value for any hand of cards. it's AN ENTRY POINT that // calls a more general, recursive helper function to figure out the value of a // hand that has been separated into a prefix and a suffix, e.g., separating [2,3,4,53,8] // into prefix = [2,3,4] and suffix = [53,8], where the prefix has NO jokers, and the // suffix MAY HAVE jokers. to get the process started, separate the hand into // an empty prefix and suffix = the given hand, i.e., [] and [2,3,4,53,8]. func value(hand: [Int]) -> Int { return value(prefix: [], suffix: hand[0...(hand.count)-1]) } // this function does the real recursion work. when called, we know that the prefix has no jokers in it. func value(prefix: ArraySlice<Int>, suffix: ArraySlice<Int>) -> Int { // if there are no cards left to move from the suffix to the prefix, just return // the ordinary value of the prefix, since it was the original hand and has no jokers if suffix.isEmpty { return noJokersValue(hand: prefix) } // otherwise, identify and remove the first card from the suffix let nextCard = suffix.first! let newSuffix = suffix.dropFirst() // if the first card in the suffix IS NOT a joker, make a direct recursive call by // removing the first card of the suffix and adding it to the prefix. if nextCard != kJoker { return value(prefix: prefix + [nextCard], suffix: newSuffix) } // but if this first card IS a joker, we recursively look through all // possible substitutions of the joker with a legitimate card, adding // each possible substitution to the prefix. we get the value // after each substitution is made, and if it's bigger than what we // had seen before, remember it var maxValue = 0 // assumes all values will be non-negative for card in 1...52 { let nextValue = value(prefix: prefix + [card], suffix: newSuffix) if nextValue > maxValue { maxValue = nextValue } } // return the largest value seen return maxValue } let x = [3,2,kJoker,4,kJoker] print(value(hand: x))one syntactic point: some arguments are typed as ArraySlice<Int>. you can certainly pass such a function a real Array of Int, but when you start using .suffix(), you get an ArraySlice, which is a fancy way of saying: "it's not a real array, but a reference you can use" that works perfectly well later when using (some) functions like .isEmpty() or additional .suffix() calls. it removes the burden of, at every level, creating a new Array in memory and it will run faster with ArraySlice.hope that helps,DMG
Feb ’20
Reply to Func fileExists finds .json but not .jpg files
hi,you wrote this for a functionfunc fileExists(url: URL) -> Bool { do { let fileManager = FileManager.default try fileManager.fileExists(atPath: String(contentsOf: url)) } catch { return false } return true }i'm not sure what you're asking here. FileManager.default.fileExists(atPath:) does not throw -- it simply returns true or false.instead, you're asking to read the String content of the incoming url to determine the filepath, with String(contentsOf: url). the file may well exist, but String(contentsOf: url) will probbaly throw for a jpg file.don't you just want the following?func fileExists(url: URL) -> Bool { return FileManager.default.fileExists(atPath: url.path) }some clarification may be needed going forward, here.hope that helps,DMG
Feb ’20
Reply to Count max consecutive integers in array
hi,i think your question has been answered quite well by Claude31, but i've looked back at this question several times -- just for fun -- and would offer alternative views ... in Swift, however (because my Objective C is a little rusty).let's say the array under consideration islet x = [2,3,5,6,7,8,10,11,12,13,17,20,21,22,23,24]option 1: (based on the fact that the array consists of integers and is sorted)consider a second array in which the index of each element is subtracted from each element. if you have a sequential run, then the run will stick out in the second array as equal integers. in this case, you would have[2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 7, 9, 9, 9, 9, 9]so we could have a slightly different question -- count the longest run of the same integer. the solution here is not much different.nevertheless, let's use these values to build a dictionary of the elements of x, keyed by this second list of values. there's a fancy, functional way to do this in Swift (1 line of code), but a procedural way to do this is the following (and this should convert to Objective C without too much trouble)var dict = Dictionary<int,[int]>() for index in 0...(x.count-1) { let key = x[index]-index if dict[key] == nil { dict[key] = [Int]() } dict[key]!.append(x[index]) }now you have all the runs identified:for key in dict.keys { print(dict[key]!.sorted(by: < ) }yes, this is more info than what you needed -- you only asked for the longest run and it's easy to go from here.option 2: (recursive)build a simple routine that identifies the longest run at the beginning of the array, and compares that recursively with the length of the longest run in what remains. under the hood, this is the same type of code solution from Claude31, except that the book-keeping is a lot simpler.func longestRun(array x: [Int]) -> Int { if x.count < 2 { return x.count } // look through the array starting from the beginning for the first // element that breaks a sequential run. that is, find the first // index with a = x[index] and b = x[index+1] where b != a + 1. // if found, the array has an opening run of length index+1, and // we can recursively find the longest run among elements starting at b. // simply report the larger of the two. for index in 0...(x.count-2) { if x[index] + 1 != x[index+1] { let xRemainder = Array(x[(index+1)...(x.count-1)]) let n = longestRun(array: xRemainder) return max(index+1, n) } } // if no such sequential break was found, the array is already a sequential run return x.count } print(longestRun(array: x))this prints 5. with not too much work, you could return the actual run from the array -- the return type would be [Int], and you'd return either the beginning of x or xRemainder, whichever was longer.hope that's of interest,DMG
Feb ’20
Reply to Carriage-return New-Line (aka linefeed)
hi,"fileContents" was in fact of type String. apologies for not making that clear. the expanded code section i used back then was: var fileContents: String do { fileContents = try String(contentsOfFile: filePath, encoding: String.Encoding.utf8) } catch let error { print("Error reading file: \(error.localizedDescription)") return nil } fileContents = fileContents.replacingOccurrences(of: "\n", with: "") let records = fileContents.split(separator: "\r").map() { String($0) }i'm sorry, it's been a while -- but i do remember this solving my particular problem at the time and being surprised that it worked.hope this helps moving forward,DMG
Jan ’20
Reply to Carriage-return New-Line (aka linefeed)
hi,i ran across a similar problem a while ago, where some of the data were coming across as "\r\n", and it was a pain to work with (the data came out of Numbers as a tab-separated file, i think).and your comment about treating "\r\n" as a single character is somewhat what i remember to be the issue.so before splitting lines by "\r", i did the following to remove all the "\n"fileContents = fileContents.replacingOccurrences(of: "\n", with: "")splitting on "\r" seemed to work OK after that.not sure if that's exactly what's the issue here ...hope that helps as well,DMG
Jan ’20
Reply to save checkmark on tableview using bool value of selected data model
hi,what your app does, really, is maintain several different lists of Workouts (each workout is a String and a Bool), according to different workout routines.an obvious choice would be to store the list of Workouts for each workout routine in UserPreferences under its own key value. for example, you could store each list under its title -- except that would be a problem in case you starting changing the names of the workout routines (e.g., if you wanted to localize the names for a different language).so i'd suggest keeping a master dictionary of workout routine names and the keys under which the associated list is stored in UserPreferences. the default setup below would work -- you can come back later to edit any title, as long as you leave the existing key alone.let keyValueDictionary: [String : String] = [ "600 Workout" : "600WorkoutKey", "5 Days for Muscle" : "5DayMuscleKey", ... "Fitness Test" : "FitnessTestKey" ]when you do viewDidLoad(), go fetch the appropriate list by its keyValue from UserPreferences (or use the default list if it's never been used and saved before).var workoutList = UserDefaults.standard.object(forKey: keyValueDictionary[navTitle]!) as? [Workout] ?? defaultWorkoutList(navTitle)you'll have to write the function defaultWorkoutList(_ navTitle: String) -> [Workout] separately, but you have the bones of that already in the body of classes such as The600Workout and so on.finally, whenever you make a change in a workout list (checking an exercise as done), write the list back to USerDefaults.func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { workout[indexPath.row].completed = !workout[indexPath.row].completed tableView.deselectRow(at: indexPath, animated: false) tableView.reloadRows(at: [indexPath], with: .automatic) // EDITED AFTER THE FACT FOR .automatic UserDefaults.standard.setValue(workoutList, forKey: keyValueDictionary[navTitle]!) }that should do it.hope that helps,DMG
Jan ’20
Reply to How to dynamically add a section for List
hi,(1) on the specific error, the problem lies in setting self.lastCompany = site.customer, since the if statement does not result in a view.(2) but the real problem is that you can't just "throw in a Section" as if it were another row. rows live within Sections in a structural sense.you have to create a list of Sections at the top level, and within each Section, create a list of rows for that section. and to do this, you have to know which Sections will be needed for your data, and within each Section, which rows belong to it.i don't know the structure of your data, but suppose it were just flat, like "a record is some information about a site and some other data." you'd have to collect all records of interest, block them out by matching sites (this determines the number of Sections), and then pull records for those sites on demand (that determines the number of rows per section) -- we'd need to know this with UIKit methods as well.with a little magical help from Swift structures, the following example works.// sample SiteInfo struct SiteInfo: Identifiable { let id = UUID() let customer: String let otherInfo: String } struct ContentView: View { // a "flat" database var siteInfoList = [ SiteInfo(customer: "Dog", otherInfo: "1"), SiteInfo(customer: "Dog", otherInfo: "2"), SiteInfo(customer: "Cat", otherInfo: "3"), SiteInfo(customer: "Bird", otherInfo: "4"), SiteInfo(customer: "Dog", otherInfo: "5"), SiteInfo(customer: "Bird", otherInfo: "6"), SiteInfo(customer: "Cat", otherInfo: "7") ] // produces a dictionary of the form // [ // "Dog" : [three SiteInfo records], // "Cat" : [two SiteInfo records], // "Bird" : [two SiteInfo records] // ] var sitesCollatedByCustomer: [String : [SiteInfo]] { Dictionary(grouping: siteInfoList, by: { $0.customer }) } // pulls out the keys from the dictionary made above, sorted var uniqueCustomers: [String] { sitesCollatedByCustomer.map({ $0.key }).sorted() } var body: some View { NavigationView { List { ForEach(uniqueCustomers, id: \.self) { customer in Section(header: Text(customer)) { ForEach(self.sitesCollatedByCustomer[customer]!) { site in SiteRow(site: site) } } } } // end of List .navigationBarTitle(Text("Site Listing")) } // end of NavigationView } }i used a trivial SiteRow view ofstruct SiteRow: View { var site: SiteInfo var body: some View { HStack { Text(site.customer) Spacer() Text(site.otherInfo) } } }that should do it (but be sure to fetch all the data you need in place of my siteInfoList). by the way: the View code is credit Paul Hudson (hackingwithswift.com). see his video series "Building a menu using List - SwiftUI Starter Project on Youtube.hope that helps,DMG
Jan ’20
Reply to save checkmark on tableview using bool value of selected data model
hi,if the data you are saving are reasonably small (sounds like an array of strings/booleans), it would make sense to save this is UserDefaults. it's quick and simple.but be sure to take a look at Paul Hudson's (hackingwithswift.com) video titled "How to save user settings using UserDefaults – Swift 5" it's available on YouTube.in short, let's assume you have something like this:struct Workout { var exercise: String var completed: Bool init(exercise: String, completed: Bool = false) { self.exercise = exercise self.completed = completed } } let defaultWorkoutList = [ Workout(exercise: "Lifting"), Workout(exercise: "Running"), Workout(exercise: "Rowing"), Workout(exercise: "Jumping"), Workout(exercise: "Stretching") ]you'll define a string as a name to identify the data you save in UserDefaults:let kUserDefaultsKey = "WorkoutStatusKey"when your app begins, or even just when your list of workouts comes on screen (viewWillAppear or even in viewDidLoad), get the data from UserDefaultsvar workoutList = UserDefaults.standard.object(forKey: kUserDefaultsKey) as? [Workout] ?? defaultWorkoutListanytime you make a change (toggling the completed boolean), be sure to addUserDefaults.standard.setValue(workoutList, forKey: kUserDefaultsKey)i think that should do it.hope that helps,DMG
Jan ’20