Swift Search UIScrollView for a UIImageView with specific attributes - swift

I have a UIScrollView with 1000 UIImages in it. These UIImages have a row and col attribute. How do I search the UIScrollView's UIImages for a specific row and col? (Then I will update the image) I am getting this error: For-in loop requires 'UIScrollView' to conform to 'Sequence'
Something like:
for eachImageView in MyUIScrollView! {
if eachImageView.row = searchrow {
if eachImageView.col = searchcol {
// do something
}
}
}
The MyUIScrollView subviews eachImageView row and col are just Int from a class:
class cellUIImageView: UIImageView {
var row: Int = 0
var col: Int = 0
...

First, a note: class names should begin upper-cased... use CellUIImageView instead of cellUIImageView.
Anyway...
You can find your view like this:
let v2 = scroll.subviews.filter {
($0 as? CellUIImageView)?.row == searchrow && ($0 as? CellUIImageView)?.col == searchcol
}
guard v2.count == 1, let foundView = v2.first as? CellUIImageView else {
print("CellUIImageView with row:", searchrow, "and col:", searchcol, "was not found!")
return
}
// do something with it, such as
foundView.image = replacementImage

Related

TableView Index out of range issue

My view controller opens a directory, counts the file types therein and stores the results in a dictionary [String:Int] of filetypes and count. I have a TableView that displays this.
The first time I open a directory the ViewController correctly displays the information in the FileTypeTableview. If I try to open another directory, execution gets to the line:
let dialogButton = dialog.runModal()
Then immediately jumps to a line in my TableView function and throws an index out of range here:
val = types[row]
Types is a [String] and shows 0 elements and row is an Int and shows 0.
I'm confused by the fact this happens at the dialog.runModal() function call.
Below are the functions that get the directory and display the TableViews.
I'm very new to Swift and MacOS programing and I'd appreciate any insights.
#IBAction func getFolder(_ sender: Any) {
let dialog = NSOpenPanel()
dialog.canChooseDirectories = true
dialog.canChooseFiles = false
allFiles = []
fileTypesDict.removeAll()
let dialogButton = dialog.runModal()
if let theURL = dialog.url {
theDirectory = theURL.path
allFilesAsPaths = getAllFiles2(atDirectoryPath: theURL.path)
allFilesAsPaths.sort()
}
fileCountLabel.stringValue = String(allFilesAsPaths.count)
mainTableView2.reloadData()
fileTypeTableView.reloadData()
}
func numberOfRows(in tableView: NSTableView) -> Int {
var numberOfRows : Int = 0
if tableView == mainTableView2 {
print("number of rows for mainTableView2")
numberOfRows = allFilesAsPaths.count
} else if tableView == fileTypeTableView {
print("number of rows for fileTypeTableView")
numberOfRows = fileTypesDict.count
} else if tableView == exifTableView {
numberOfRows = exifData.count
print("exifData.count = \(exifData.count)")
print("number of rows for exifTableview \(numberOfRows)")
}
return numberOfRows
}
func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
var val : String = ""
if tableView == mainTableView2 {
val = allFilesAsPaths[row]
}
if tableView == exifTableView {
if tableColumn?.identifier.rawValue == "tag" {
let tagArray : [String] = Array(exifData.keys)
val = tagArray[row]
} else if tableColumn?.identifier.rawValue == "value" {
let valueArray : [String] = Array(exifData.values)
val = valueArray[row]
}
}
if tableView == fileTypeTableView {
let types : [String] = Array(fileTypesDict.keys)
let counts : [Int] = Array(fileTypesDict.values)
if tableColumn?.identifier.rawValue == "type" {
val = types[row]
} else {
val = String(counts[row])
}
}
return val
}
I think you should try to remove
allFiles = []
fileTypesDict.removeAll()
And simply set all your arrays at once after dialog validation. Because it looks like presenting the dialog triggers an update of the table, but at this time, your objects are not sync because you do some work before, and after.
BTW, it would be clearer if you do separate table classes than dealing with if tableView == ... {}. You take the risk that your code gradually become inextricable. I've been there already ;)

SwiftUI - Is it possible to change an ActionSheet button text after it is displayed?

I would like to show an ActionSheet containing InApp purchase objects the user can purchase.
But I want that sheet to contain the prices of such objects, like:
Object 1 ($1.99)
Object 2 ($2.99)
...
but the price is asynchronous, cause it has to be retrieved from the store.
So, I thought about doing this:
struct Package {
enum Packtype:String {
typealias RawValue = String
case obj1 = "com.example.object1"
case obj2 = "com.example.object2"
}
var productID:String = ""
#State var namePriceString:String = ""
init(productID:String) {
self.productID = productID
}
}
then, when I create the action sheet button I do this:
var obj1 = Package(productID: Package.Packtype.obj1.rawValue)
var obj2 = Package(productID: Package.Packtype.obj2.rawValue)
self.getPrices(packages:[obj1, obj2])
let obj1Button = ActionSheet.Button.default(Text(obj1.$namePriceString)) {
// do something with obj1
}
let obj2Button = ActionSheet.Button.default(Text(obj2.$namePriceString)) {
// do something with obj1
}
// build the actionsheet
later in the code:
func getPrices(packages:[Package]) {
let productIDS = Set(packages.map {$0.productID})
SwiftyStoreKit.retrieveProductsInfo(productIDS) { (answer) in
if answer.invalidProductIDs.first != nil { return }
let results = answer.retrievedProducts
if results.count == 0 { return }
for result in answer {
if let package = packages.filter({ ($0.productID == result.productIdentifier) }).first {
package.namePriceString = result.localizedTitle + "(" + "\(result.localizedPrice!)" + ")"
}
}
}
}
I have an error pointing to Text on the button creation lines saying
Initializer 'init(_:)' requires that 'Binding' conform to
'StringProtocol'
In a nutshell I need this:
I display the actionsheet. Its buttons contain no price.
I retrieve the prices
Actionsheet buttons are updated with the prices.
A possible solution is to return prices in a completion handler and only then display the action sheet:
struct ContentView: View {
#State var showActionSheet = false
#State var localizedPrices = [Package: String]()
var body: some View {
Button("Get prices") {
getPrices(packages: Package.allCases, completion: {
localizedPrices = $0
showActionSheet = true
})
}
.actionSheet(isPresented: $showActionSheet) {
let buttons = localizedPrices.map { package, localizedPrice in
ActionSheet.Button.default(Text(localizedPrice), action: { buy(package: package) })
}
return ActionSheet(title: Text("Title"), message: Text("Message"), buttons: buttons + [.cancel()])
}
}
}
func getPrices(packages: [Package], completion: #escaping ([Package: String]) -> Void) {
// simulates an asynchronous task, should be replaced with the actual implementation
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
let localizedPrices = Dictionary(uniqueKeysWithValues: packages.map { ($0, "\(Int.random(in: 1 ..< 100))") })
completion(localizedPrices)
}
}
func buy(package: Package) {
print("Buying \(package.rawValue)")
}
enum Package: String, CaseIterable {
case obj1 = "com.example.object1"
case obj2 = "com.example.object2"
}
This can be further tuned with loading animations etc...

Implementing search result for searching inside dictionary?

I have a dictionary like,
var DataDict:[String:[String]] = [String:[String]]()
DataDict[“fruits”] = [“apple”,”orange”,”pineapple”,”grape”]
DataDict[“vehicle”] = [“car”,”cycle / scooter“,”bike”]
DataDict[“colours”] = [“black”,”white”,”yellow”,”green”,”blue”]
so when I search in the search bar, if the searchText is fruits then the tableview should display the full array of fruits, or else whatever the searchText matches to the single object inside each key of DataDict?
So how will I achieve that to display the tableview. I need to implement that in searchBar textDidChange delegate.
Finllay I need to the display the result as, DataDict object as title and It's respective key as Subtitle.
ex:
apple
fruits
pineapple
fruits
You can do like this,
var DataDict:[String:[String]] = [String:[String]]()
DataDict["fruits"] = ["apple","orange","pineapple","grape"]
DataDict["vehicle"] = ["car","cycle / scooter","bike"]
DataDict["colours"] = ["black","white","yellow","green","blue"]
let filterarray = Array(DataDict.keys).filter { $0.contains("searchText")}
print("\(filterarray)")
for string in filterarray {
print("\(DataDict[string]!)")
}
Now you can display the as your requirement with filterarray
try below method for searchingtext. Result can be displayed to table
func searchText(string:String) -> [String] {
let text = string
var DataDict:[String:[String]] = [String:[String]]()
DataDict["fruits"] = ["apple","orange","pineapple","grape"]
DataDict["vehicle"] = ["car","cycle / scooter","bike"]
DataDict["colours"] = ["black","white","yellow","green","blue"]
var searchedItems = [String]()
for key in DataDict.keys {
if text == key {
if let items = DataDict[key] {
searchedItems.removeAll()
searchedItems.append(contentsOf: items)
}
break;
}
else {
if let items = DataDict[key] {
let filterd = items.filter({ (x) -> Bool in
return x.lowercased().contains(text.lowercased())
})
if filterd.count > 0 {
searchedItems.append(contentsOf: filterd)
}
}
}
}
print("SearchedItems: \(searchedItems)")
return searchedItems
}
You may get array in this way also for faster response..
let arrTemp = Array(DataDict.keys).filter { $0.contains(searchBar.text!)}
print(DataDict[arrTemp[0]])
Hope it will work for you.. :)
I know its kind of late , but wanted to post an improved answer with the use of flatMap and filter.
var DataDict:[String:[String]] = [String:[String]]()
DataDict["fruits"] = ["apple","orange","pineapple","grape"]
DataDict["vehicle"] = ["car","cycle/scooter","bike"]
DataDict["colours"] = ["black","white","yellow","green","blue"]
let searchText = "orange"
func search() {
var resultsArray = [String]()
if DataDict[searchText] != nil {
resultsArray = DataDict[searchText] ?? []
}else {
resultsArray = DataDict.flatMap{$0.1}.filter{ $0 == searchText}
}
print(resultsArray)
}
With the use of flat map you do not need to iterate each array of string as it flattens your nested dict into one. For more info on flatMap https://medium.com/#abhimuralidharan/higher-order-functions-in-swift-filter-map-reduce-flatmap-1837646a63e8
Hope this helps.
Create Empty array and initialize it will first key of the DataDict and make it the dataSource of the tableView
then every search replace it's contents with the new one that matches the search and reload the tableView

How to rewrite code for a (too) big IF statement?

I have a project with some UIbuttons with different UIimages displayed in it. Through user interaction, there could be any of the UIimages in the UIButtons. There are like around 1000 images in the project. I have initialised a variable named 'i'. And a IBAction named buttonTapped on all the buttons. Now I want to update variable 'i' and use the value of 'i' for every different possible `UIImage'. I can do this with an IF statement as shown here:
#IBAction func buttonTapped(sender: UIButton) {
if sender.currentImage == UIImage(named: "image1") {
i = 1
print(i)
// use the value of i
} else if sender.currentImage == UIImage(named: "image2") {
i = 2
print(i)
// use the value of i
} else if sender.currentImage == UIImage(named: "image3") {
i = 3
print(i)
// use the value of i
} else if // and so on
But I would like a better solution then an IF statement with around 1000 else if(s). I have tried, but I am not able to rewrite the code in a concise matter. What could I use instead of the IF statement? Some kind of loop?
A crude solution (assuming the indices are all sequential) would be
for i in 1 ... 1000 { // or whatever the total is
if sender.currentImage == UIImage(named: "image\(i)") {
print(i)
// use i
}
}
A better solution, especially if the names are not in the format you give, is to have an array of structs (or just an array of images, if the numbers are all sequential)...
struct ImageStruct {
var image: UIImage
var index: Int
}
var imageStructs:[ImageStruct]... // Some code to fill these
...
#IBAction func buttonTapped(sender: UIButton) {
let matches = self.imageStructs.filter( { $0.image == sender.currentImage } )
if let match = matches.first {
// use match.index
}
}

How to find UITextView number of lines

I have to find the number of lines of a UITextView. There is no property available, like anumberOfLines, on UITextView. I use the following formula, but it not doesn't work. Does anybody have an idea about this?
int numLines = txtview.contentSize.height/txtview.font.lineHeight;
If you are using iOS 3, you need to use the leading property:
int numLines = txtview.contentSize.height / txtview.font.leading;
If you are using iOS 4, you need to use the lineHeight property:
int numLines = txtview.contentSize.height / txtview.font.lineHeight;
And, as #thomas pointed out, be careful of rounding if you need an exact result.
Swift 4 way to calculate number of lines in UITextView using UITextInputTokenizer:
public extension UITextView {
/// number of lines based on entered text
public var numberOfLines: Int {
guard compare(beginningOfDocument, to: endOfDocument).same == false else {
return 0
}
let direction: UITextDirection = UITextStorageDirection.forward.rawValue
var lineBeginning = beginningOfDocument
var lines = 0
while true {
lines += 1
guard let lineEnd = tokenizer.position(from: lineBeginning, toBoundary: .line, inDirection: direction) else {
fatalError()
}
guard compare(lineEnd, to: endOfDocument).same == false else {
break
}
guard let newLineBeginning = tokenizer.position(from: lineEnd, toBoundary: .character, inDirection: direction) else {
fatalError()
}
guard compare(newLineBeginning, to: endOfDocument).same == false else {
return lines + 1
}
lineBeginning = newLineBeginning
}
return lines
}
}
public extension ComparisonResult {
public var ascending: Bool {
switch self {
case .orderedAscending:
return true
default:
return false
}
}
public var descending: Bool {
switch self {
case .orderedDescending:
return true
default:
return false
}
}
public var same: Bool {
switch self {
case .orderedSame:
return true
default:
return false
}
}
}
You can look at the contentSize property of your UITextView to get the height of the
text in pixels, and divide by the line spacing of the UITextView's font to get the
number of text lines in the total UIScrollView (on and off screen), including both wrapped and line broken text.
int numLines = txtview.contentSize.height/txtview.font.leading;