Search in struct, delivering index of array? - swift

What's needed to get this code running?
Hi, I already have this part of code. It searches in an array of struct and delivers - if found - the index of that item:
for index in 0 ..< gSteering.count {
if gSteering[index].Ext == fileExtension.uppercaseString {
priority = index
break
}
}
I'm sure, that there is are shoreter and more elegant way in SWIFT using library functions. Any hints?

Something like
let priority = gSteering.indexOf() {
$0.Ext == fileExtension.uppercaseString
}
P.S. And if you want priority to default to maxint in case if item is not found:
let priority = gSteering.indexOf() {
$0.Ext == fileExtension.uppercaseString
} ?? Int.max

Here's one I could come up with:
if let index = (gSteering.map{ $0.Ext }).indexOf(fileExtension.uppercaseString)
{
priority = index
}
else
{
// Not found.
}
And here's another one:
let priority = gSteering.indexOf { $0.Ext == fileExtension.uppercaseString }
And here's one to get the object directly instead of the index:
// This will give you an array with all the results that match.
let priorityObj = gSteering.filter { $0.Ext == fileExtension.uppercaseString }

Related

Moving Table rows in SwiftUI

Is it possible to support moving rows in a SwiftUI Table view?
I know there's List which can optionally support selection and drag-and-drop to move single or multiple rows. Since it seems similar, I would like to do this with a Table too, but I wasn't able to find any way to do this. Is this possible in SwiftUI? And if it is, what's the best way to do it?
Where I started to figure this out was the WWDC 2021 session "SwiftUI on the Mac: Finishing Touches". I highly recommend this video, as well as the preceding one "SwiftUI on the Mac: Build the Fundamentals". The code for both sessions is available.
Since you didn't include your code to show what you want to do, I have to use my code. I have a table based on an array of an Identifiable struct called Channel. Among a number of fields which are irrelevant to this problem, there is a field "id" of type UUID.
Following the model of the WWDC video, I made an extension to Channel:
import UniformTypeIdentifiers
extension Channel {
static var draggableType = UTType(exportedAs: "com.yourCompany.yourApp.channel")
// define your own type here. don't forget to include it in your info.plist as an exported type
static func fromItemProviders(_ itemProviders: [NSItemProvider], completion: #escaping ([Channel]) -> Void) {
let typeIdentifier = Self.draggableType.identifier
let filteredProviders = itemProviders.filter {
$0.hasItemConformingToTypeIdentifier(typeIdentifier)
}
let group = DispatchGroup()
var result = [Int: Channel]()
for (index, provider) in filteredProviders.enumerated() {
group.enter()
provider.loadDataRepresentation(forTypeIdentifier: typeIdentifier) { (data, error) in
defer { group.leave() }
guard let data = data else { return }
let decoder = JSONDecoder()
guard let channel = try? decoder.decode(Channel.self, from: data)
else { return }
result[index] = channel
}
}
group.notify(queue: .global(qos: .userInitiated)) {
let channels = result.keys.sorted().compactMap { result[$0] }
DispatchQueue.main.async {
completion(channels)
}
}
}
var itemProvider: NSItemProvider {
let provider = NSItemProvider()
provider.registerDataRepresentation(forTypeIdentifier: Self.draggableType.identifier, visibility: .all) {
let encoder = JSONEncoder()
do {
let data = try encoder.encode(self)
$0(data, nil)
} catch {
$0(nil, error)
}
return nil
}
return provider
}
}
This makes an item in the table draggable. Of course, that does no good if there's nothing that will accept the drag. So, you have to make a change to your Table.
Table(selection: $selection, sortOrder: $sortOrder) {
// for clarity, I've removed the table columns
} rows: {
ForEach(document.channels) { channel in
TableRow(channel)
.itemProvider { channel.itemProvider }
}
.onInsert(of: [Channel.draggableType]) { index, providers in
Channel.fromItemProviders(providers) { channels in
document.channels.insert(contentsOf: channels, at: newIndex)
}
}
}
}
Now that will enable you to drag item or items from one window to another. You can, of course, drag within a table now, too. Unfortunately, you will end up making a copy in the new place. Not what you want to do in most cases. How to fix this? Delete the original copy! Of course, you can also run into the problem of indexing in the right place, and if you drag more than one item (from a discontinuous selection, even worse!), the results become, shall we say, undefined.
I still wanted to be able to drag multiple items from another table, so the final onInsert becomes a little more complex (Which I'm sure could be cleaned up a bot further):
Channel.fromItemProviders(providers) { channels in
var newIndex = index
let intraTableDrag = document.channels.contains(where: {$0.id == channels[0].id})
if intraTableDrag && channels.count == 1 {
let oldIndex = document.channels.firstIndex(where: {$0.id == channels[0].id})
if newIndex > oldIndex! {
newIndex -= 1
}
for channel in channels {
let channelID = channel.id
removeChannel(withID: channelID)
}
let maxIndex = document.channels.count
if index > maxIndex {
newIndex = maxIndex
}
}
if (intraTableDrag && channels.count == 1) || !intraTableDrag {
document.channels.insert(contentsOf: channels, at: newIndex)
document.setChannelLocationToArrayOrder()
}
}
}
I hope this is enough to get you started. Good luck!

Dictionary in Dictionary value search

I am downloading information from a Firebase database and it is being inputted via a for loop into:
static var Reports = [String:[String:String]]()
I need to figure out a way to search the inside values for a certain string
I have messed around with this but can't seem to get it inside the inside dictionary (If that makes sense)
for values in Reports.count {
if let item = Reports["favorite drink"] {
print(item)
}
}
I need to have a search string then a number of times the value appears like so:
func findString(dict Dictionary) -> Int {
var ReportsLevel1 = 0
(for loop I'm guessing)
search here for string
return ReportsLevel1
}
Tip: the outside dictionary keys are not set in stone, they depend on what time and date the report was submitted
To find out the numberOfTimes in which "yourSearchString" appears you can do as follows
var numberOfTimes = 0
for internalDictionary in reports.values
{
for value in internalDictionary.values
{
if (value == "yourSearchString") { numberOfTimes += 1 }
}
}
or
let numberOfTimes = reports.flatMap { internalDictsArray in internalDictsArray.value.filter { $0.value == "yourSearchString" } }.count

Using Core Data to Increment a count on an entity (Twitter CS193p)

I'm completing the CS193 Stanford course, and am using Core Data to store tweets as part of a Twitter client.
However, when I find a hashmention that is existing, I want to increment the hash.count representing how many matches I have, but no matter how many matching hashmentions there are hash.count only ever stores 0 or 2 (i.e. the attribute is not functioning as persistent storage on the entity).
class HashMention: NSManagedObject {
static func findOrCreateHashMention(matching twitterInfo: Twitter.Mention, in context: NSManagedObjectContext) throws -> HashMention
{
let hash = HashMention (context: context)
let request : NSFetchRequest<HashMention> = HashMention.fetchRequest()
request.predicate = NSPredicate(format: "text =[cd] %#", twitterInfo.keyword)
do {
let matches = try context.fetch(request)
if matches.count > 0 {
//inc count
hash.count = Int32(Int(matches.count) + 1)
return hash
}
else{
hash.count = 0
print("zero hash:", twitterInfo.keyword)
hash.text = twitterInfo.keyword.lowercased()
return hash
}
}
catch{
//makes this function throw
throw error
}
}
}
So matches itself needed to be changed - but is in an array in the example above. Therefore the answer was the following:
do {
let matches = try context.fetch(request)
let mention = matches.first
if matches.count > 0 {
mention?.count = (mention?.count)! + 1
//.. more code

Using guard with a non-optional value assignment

This is not a question about optional arrays, as one can see in the answers.
I like using guard because it makes your intensions clear. I've used it both for the optional version like this...
guard let c = MyOptionalArray else { return }
as well as for more traditional bounds checking on non-optionals...
guard MyArray.count > 0 else { return }
But now I'd like to use that count in following code. So I did...
guard let c = MyArray.count > 0 else { return }
which doesn't work, obviously, so I did what should...
guard let c = parts.count where c > 1 else { return }
But that says Initializer for conditional binding must have Optional type, not 'Int'. Now I understand the error, and tried a bunch of seemingly obvious changes to the format, but no go. Is there no way to use guard as an assignment on a non-optional value? This seems like something it should be able to do.
If you throw a case in there, it'll work. So as follows:
guard case let c = parts.count where c > 1 else { return }
You can initialize an optional wrapping the non-optional:
guard let count = Optional([42].count), count > 0 else {
return
}
guard let count = .some([42].count), count > 0 else {
return
}
or cast it to an optional:
guard let count = [42].count as Int?, count > 0 else {
return
}
As mentioned in other answers, guard case let also works:
guard case let count = [42].count, count > 0 else {
return
}
guard case let count = [42].count where count > 0 else {
return
}

Conditional binding on array

Is there a way to use if let on an array. If the array has a value at index let it equal this value.
if let view = self.view.subviews({$0.tag == 1 })[0] {
view.backgroundColor = UIColor.blackColor()
} else {
print("No view with tag 1")
}
You can use the filter functionality, and use first instead of [0], to avoid crashes if the filtered array has no elements:
if let view = self.view.subviews.filter{ $0.tag == 1 }.first {
Swift 3 added the Sequence.first(where:) method, which can replace the calls to filter and first in Cristik's answer.
The advantages of first(where:) over filter { ... }.first are (a) the code becomes marginally clearer and (b) more importantly, better performance when the array is large or the closure is expensive because it stops after finding the first match.
if let view = self.view.subviews.first { $0.tag == 1 } {
...
}