refactor suggestion for my array iteration? - ios

i feel like there is a more elegant way what i have done below.. Any suggestion ?
import Foundation
var bullet = ["aka","bz","cy"]
for i in bullet {
if i.contains("ka") {
let p = bullet.firstIndex(of: i)
bullet[p!]
}
}

If you want to get the first matching value, then you can use first, if you want to get it's index then, you can use firstIndex and if let is to safely unwrap an optional value.
var bullet = ["aka","bz","cy"]
if let value = bullet.first(where: { $0.contains("ka") }) {
print(value) //"aka"
}
if let index = bullet.firstIndex(where: { $0.contains("ka")}) {
print(index) //0
}

You can use filter(_:) along with contains(_:) like,
let arr = bullet.filter { $0.contains("ka") }

You can do it like this:
if let kaItem = bullet.first({ $0.contains(“ka”) }) {
myLabel.text = kaItem
} else {
myLabel.text = “asdfghj”
}
If you need to get the index and set the text, you can use firstIndex method:
if let kaItemIndex = bullet.firstIndex({ $0.contains(“ka”) }) {
myLabel.text = bullet[kaItemIndex]
} else {
myLabel.text = “asdfghj”
}

Related

Get a random unique element from an Array until all elements have been picked in Swift

I have the following array:
var notebookCovers = ["cover1", "cover2", "cover3", "cover4", "cover4", "cover6", "cover7", "cover8", "cover9", "cover10"]
and a UIButton that when it's pressed it generates a new UIImage with one of the elements of the array.
What I need to do is every time the button is tapped to generate random but unique element from the array (without repeating the elements) until they've all been selected and then restart the array again.
So far, I have it getting a random element but it's repeated and I cannot figure out how to it so it gets a unique image every time
func createNewNotebook() {
let newNotebook = Notebook()
let randomInt = randomNumber()
newNotebook.coverImageString = notebookCovers[randomInt]
notebooks.insert(newNotebook, at: 0)
collectionView.reloadData()
}
func randomNumber() -> Int {
var previousNumber = arc4random_uniform(UInt32(notebookCovers.count))
var randomNumber = arc4random_uniform(UInt32(notebookCovers.count - 1))
notebookCovers.shuffle()
if randomNumber == previousNumber {
randomNumber = UInt32(notebookCovers.count - 1)
}
previousNumber = randomNumber
return Int(randomNumber)
}
Set is a collection type that holds unique elements. Converting your notebooks array to Set also lets you take advantage of its randomElement function
var aSet = Set(notebooks)
let element = aSet.randomElement()
aSet.remove(element)
Copy the array. Shuffle the copy. Now just keep removing the first element until the copy is empty. When it is empty, start over.
Example:
let arr = [1,2,3,4,5]
var copy = [Int]()
for _ in 1...30 { // just to demonstrate what happens
if copy.isEmpty { copy = arr; copy.shuffle() }
let element = copy.removeFirst() ; print(element, terminator:" ")
}
// 4 2 3 5 1 1 5 3 2 4 4 1 2 3 5 1 4 5 2 3 3 5 4 2 1 3 2 4 5 1
If you want to create a looping solution:
let originalSet = Set(arrayLiteral: "a","b","c")
var selectableSet = originalSet
func repeatingRandomObject() -> String {
if selectableSet.isEmpty {
selectableSet = originalSet
}
return selectableSet.remove(selectableSet.randomElement()!)!
}
force unwrapping is kind of safe here, since we know that the result will never be nil. If you don't want to force unwrap:
let originalSet = Set(arrayLiteral: "a","b","c")
var selectableSet = originalSet
func repeatingRandomObject() -> String? {
if selectableSet.isEmpty {
selectableSet = originalSet
}
guard let randomElement = selectableSet.randomElement() else { return nil }
return selectableSet.remove(randomElement)
}
You can try something like this,
var notebookCovers = ["cover1", "cover2", "cover3", "cover4", "cover4", "cover6", "cover7", "cover8", "cover9", "cover10"]
var tappedNotebooks: [String] = []
func tapping() {
let notebook = notebookCovers[Int.random(in: 0...notebookCovers.count - 1)]
if tappedNotebooks.contains(notebook){
print("already exists trying again!")
tapping()
} else {
tappedNotebooks.append(notebook)
print("appended", notebook)
}
if tappedNotebooks == notebookCovers {
tappedNotebooks = []
print("cleared Tapped notebooks")
}
}
It is possible with shuffle:
struct AnantShuffler<Base: MutableCollection> {
private var collection: Base
private var index: Base.Index
public init?(collection: Base) {
guard !collection.isEmpty else {
return nil
}
self.collection = collection
self.index = collection.startIndex
}
public mutating func next() -> Base.Iterator.Element {
let result = collection[index]
collection.formIndex(after: &index)
if index == collection.endIndex {
collection.shuffle()
index = collection.startIndex
}
return result
}
}
fileprivate extension MutableCollection {
/// Shuffles the contents of this collection.
mutating func shuffle() {
let c = count
guard c > 1 else { return }
for (firstUnshuffled, unshuffledCount) in zip(indices, stride(from: c, to: 1, by: -1)) {
let d: Int = numericCast(arc4random_uniform(numericCast(unshuffledCount)))
let i = index(firstUnshuffled, offsetBy: d)
swapAt(firstUnshuffled, i)
}
}
}
Use:
let shuffler = AnantShuffler(collection: ["c1","c2","c3"].shuffled())
shuffler?.next()

How to extract street, city, etc. from GMSPlace Address Components

I'm using Google Places API for iOS and can successfully retrieve nearby places and present the address as a string. What I'm trying to do is extract address components such as city to store in a database.
The documentation indicates a GMSPlace has an addressComponents property (an array), but I can't seem to figure how to use this property.
The code below provides sets the entire address to the text of a label, but I can't get beyond that:
Edit ---- added code that shows how I'm trying to access Address Components
venueLabel.isHidden = false
venueLabel.text = selectedPlace?.name
addressLabel.isHidden = false
addressLabel.text = selectedPlace?.formattedAddress
let addressComponents = selectedPlace?.addressComponents
for component in addressComponents! {
let city = component["city"] //Error Type GMSPaceAddressComponent has no subscript members
print(city)
}
A safe Swift 4.2 solution:
let name = place.addressComponents?.first(where: { $0.type == "city" })?.name
selectedPlace.addressComponents is a array of GMSAddressComponent which have 2 properties type and name.
In you case it will be like
for component in addressComponents! {
if component.type == "city" {
print(component.name)
}
}
GMSAddressComponent is a class not a dictionary or array that's you are getting this error.
Additional component types can be referred from the link.
Swift 4
var keys = [String]()
place.addressComponents?.forEach{keys.append($0.type)}
var values = [String]()
place.addressComponents?.forEach({ (component) in
keys.forEach{ component.type == $0 ? values.append(component.name): nil}
})
print(values)
For the latest Google Places API (July 2019) this is what could help you.
Actually, Google now putting a couple of types for each element. So the "type" is now deprecated and "types" is new filed.
You can do something like this:
for addressComponent in (self.mySelectedPlace?.addressComponents)! {
for type in (addressComponent.types){
switch(type){
case "country":
var country = addressComponent.name
case "postal_code":
var postCode = addressComponent.name
default:
break
}
}
}
It's working in Swift 5 and iOS 13.3
1. Create a function for showing GMSAutocompleteViewController
func googlePlacesSearchVC() {
let acController = GMSAutocompleteViewController()
acController.delegate = self
// Specify the place data types to return.
let fields: GMSPlaceField = GMSPlaceField(rawValue: UInt(GMSPlaceField.name.rawValue) |
UInt(GMSPlaceField.formattedAddress.rawValue) |
UInt(GMSPlaceField.addressComponents.rawValue))!
acController.placeFields = fields
// Specify a filter.
let filter = GMSAutocompleteFilter()
filter.country = "IN"
acController.autocompleteFilter = filter
acController.secondaryTextColor = .darkGray
acController.primaryTextColor = .lightGray
acController.primaryTextHighlightColor = .black
acController.tableCellBackgroundColor = .whiteThree
acController.tableCellSeparatorColor = .lightGray
// Display the autocomplete view controller.
present(acController, animated: true) {
let views = acController.view.subviews
let subviewsOfSubview = views.first!.subviews
let subOfNavTransitionView = subviewsOfSubview[1].subviews
let subOfContentView = subOfNavTransitionView[1].subviews
let searchBar = subOfContentView[0] as! UISearchBar
searchBar.searchTextField.placeholder = "places_picker_hint_add_address".localized
searchBar.searchBarStyle = .minimal
searchBar.searchTextField.font = UIFont.screenTitle16Pt
searchBar.searchTextField.backgroundColor = UIColor.white
searchBar.searchTextField.leftView?.tintColor = .darkGray
searchBar.delegate?.searchBar?(searchBar, textDidChange: "")
}
}
2. Call that function where ever you need, for ex:-
override func viewDidLoad() {
super.viewDidLoad()
googlePlacesSearchVC()
}
3. Call GMSAutoCompleteViewControllerDelegates Methods.
Here will get all details like Street, City, etc. from GMSPlace Address Components
extension ViewController {
// Handle the user's selection.
func viewController(_ viewController: GMSAutocompleteViewController, didAutocompleteWith place: GMSPlace) {
// Show HouseAndFlat
if place.name?.description != nil {
yourTxtField.text = place.name?.description ?? ""
}
// Show latitude
if place.coordinate.latitude.description.count != 0 {
var latitude = place.coordinate.latitude
}
// Show longitude
if place.coordinate.longitude.description.count != 0 {
selectedLongitude = place.coordinate.longitude
}
// Show AddressComponents
if place.addressComponents != nil {
for addressComponent in (place.addressComponents)! {
for type in (addressComponent.types){
switch(type){
case "sublocality_level_2":
yourTxtField.text = addressComponent.name
case "sublocality_level_1":
yourTxtField.text = addressComponent.name
case "administrative_area_level_2":
yourTxtField.text = addressComponent.name
case "administrative_area_level_1":
yourTxtField.text = addressComponent.name
case "country":
yourTxtField.text = addressComponent.name
case "postal_code":
yourTxtField.text = addressComponent.name
default:
break
}
}
}
}
dismiss(animated: true, completion: nil)
}
func viewController(_ viewController: GMSAutocompleteViewController, didFailAutocompleteWithError error: Error) {
// TODO: handle the error.
print("Error: ", error.localizedDescription)
}
// User canceled the operation.
func wasCancelled(_ viewController: GMSAutocompleteViewController) {
dismiss(animated: true, completion: nil)
}
}
I have found an answer from this link
I am using this extension:
extension Array where Element == GMSAddressComponent {
func valueFor(placeType: String, shortName: Bool = false) -> String? {
// bug in the google or apple framework. This cast must stay.
// Withou it crashing.
guard let array = self as? NSArray else { return nil }
let result = array
.compactMap { $0 as? GMSAddressComponent }
.first(where: {
$0.types
.first(where: { $0 == placeType }) == placeType
})
return shortName ? result?.shortName : result?.name
}
}
Usage:
let place: GMSPlace
// ...
place.addressComponents?.valueFor(placeType: kGMSPlaceTypeRoute, shortName: true)
I will extend #Ramis answer, in any case you want to check for multiple types if one of them exist, e.x
addressComponents?.valueFor(placeTypes: kGMSPlaceTypePostalTown, kGMSPlaceTypeLocality)
implementation
extension Array where Element == GMSAddressComponent {
func valueFor(placeTypes: String..., shortName: Bool = false) -> String? {
let array = self as NSArray
let result = array
.compactMap { $0 as? GMSAddressComponent }
.first(where: {
placeTypes.contains($0.types.first(where: { placeTypes.contains($0) }) ?? "")
})
return shortName ? result?.shortName : result?.name
}
}
I could not find this anywhere for Objective-C, for those who follow behind me:
for (int i = 0; i < [place.addressComponents count]; i++)
{
NSLog(#"name %# = type %#", place.addressComponents[i].name, place.addressComponents[i].type);
}
for ( GMSAddressComponent * component in place.addressComponents) {
if ( [component.type isEqual: #"locality"] )
{
NSLog(#"Current city %# ", component.name);
}
}
var keys = [String]()
place.addressComponents?.forEach{keys.append($0.type)}
var values = [String]()
place.addressComponents?.forEach({ (component) in
keys.forEach{ component.type == $0 ? values.append(component.name): nil}
print("All component type \(component.type)")
if component.type == "locality"{
print("This is city name \(component.name)")
}
})
print(values)
I found this, I hope I help you
let arrays : NSArray = place.addressComponents! as NSArray
for i in 0..<arrays.count {
let dics : GMSAddressComponent = arrays[i] as! GMSAddressComponent
let str : NSString = dics.type as NSString
if (str == "country") {
print("Country: \(dics.name)")
self.pais = dics.name
}
else if (str == "administrative_area_level_2") {
print("City: \(dics.name)")
self.ciudad = dics.name
}
print("City: (String(describing: place.addressComponents?.first(where: {$0.type == "locality"})?.name))")
print("Postal Code: (String(describing: place.addressComponents?.first(where: {$0.type == "postal_code"})?.name))")
print("State: (String(describing: place.addressComponents?.first(where: {$0.type == "administrative_area_level_1"})?.name))"
Use this description for work like this.
https://developers.google.com/maps/documentation/places/ios-sdk/place-data-fields

Swift display annotations in mapView in another thread

This code does not add annotations to mapView. I saw in one answer that mapView function is called every time addAnotation is called so where's the problem? But when I move map they show up.
func addPlacesMarkers(location:CLLocation) {
self.communication.getJsonData(self.getPubsNearByCreator(location)) { (finalData, error) -> Void in
if error != nil {
print(error)
} else {
if let row: NSArray = finalData {
for var i = 0; i < row.count; i++ {
let lat = row[i]["lat"] as! String
let lng = row[i]["lng"] as! String
let title = row[i]["name"] as! String
let id = row[i]["id"] as! String
let point = CustomizedAnotation(id: Int(id)!, name: title)
point.title = title
point.coordinate.latitude = Double(lat)!
point.coordinate.longitude = Double(lng)!
let keyExists = self.places[Int(id)!] != nil
if keyExists == false {
self.places.updateValue(point, forKey: Int(id)!)
}
}
var finalPlaces :[MKPointAnnotation] = []
for place in self.places.values {
finalPlaces.append(place)
}
self.mView.addAnnotations(finalPlaces)
self.mView.showsPointsOfInterest = false
}
}
}
}
You can't modify the UI in a thread different from the main.
You should put your UI modification code inside a dispatch_async block like this:
dispatch_async(dispatch_get_main_queue()) {
//Your code that modify the UI
self.mView.addAnnotations(finalPlaces)
}

Returning first 3 values in a array

I have a array that looks like this:
var myArray = ["1one", "1two", "1three", "1four", "1five", "1six"]
And to get random, I use this:
var originalNames = [String]()
func getRandomName() -> String {
if (names.count == 0) {
names = originalNames
}
let randomNumber = Int(arc4random_uniform(UInt32(names.count)))
return names.removeAtIndex(randomNumber)
}
And I use it like this:
self.randomLabel.text = getRandomName()
As you can see, the array contains six different strings. The code that I am currently using, will return add the strings inside the array at random, but I only want to return the first 3 strings randomly. How can I do this?
You can try using
var originalNames = [String]()
func getRandomName() -> String {
if (names.count == 0) {
names = originalNames
}
let randomNumber = Int(arc4random_uniform(3))
return names[randomNumber]
}
so let randomNumber = Int(arc4random_uniform(3)) this will return random Int value upto 3.
var myArray = ["1one", "1two", "1three", "1four", "1five", "1six"]
var result:[String] = []
while result.count < 3 {
let randomNumber = Int(arc4random_uniform(UInt32(myArray.count)))
result.append(myArray.removeAtIndex(randomNumber))
}
print(result) // "["1two", "1one", "1three"]\n"
if you don't want to modify the original array just make a copy of it
let myArray = ["1one", "1two", "1three", "1four", "1five", "1six"]
var inputNames = myArray
var result:[String] = []
while result.count < 3 {
result.append(inputNames.removeAtIndex(Int(arc4random_uniform(UInt32(inputNames.count)))))
}
print(result) // "["1six", "1two", "1one"]\n"

'SKNode?' does not have a member named 'position'

What am I doing wrong? I can't seem to figure this out. I have tried putting an exclamation mark behind: var thisBlock = self.childNodeWithName(block),
this gives me a new error saying. type () does not confirm to protocol 'BooleanType'.
func blockRunner() {
for(block, blockStatus) in self.blockStatuses {
var thisBlock = self.childNodeWithName(block)
if blockStatus.shouldRunBlock() {
blockStatus.timeGapForNextRun = random()
blockStatus.currentInterval = 0
blockStatus.isRunning = true
}
if blockStatus.isRunning {
if thisBlock.position.x = blockMaxX{
thisBlock.position.x -= CGFloat(self.groundSpeed)
} else {
thisBlock.position.x = self.origBlockPositionX
blockStatus.isRunning = false
self.score++
if ((self.score % 5) == 0) {
self.groundSpeed++
}
self.scoreText.text = String(self.score)
}
} else {
blockStatus.currentInterval++
}
}
}
childNodeWithName() does return an optional SKNode? which you have to unwrap to use. I don't know why var thisBlock = self.childNodeWithName(block)! didn't solve your issue. I would recommend using optional binding (if let) syntax:
if let thisBlock = self.childNodeWithName(block) {
if blockStatus.shouldRunBlock() {
blockStatus.timeGapForNextRun = random()
blockStatus.currentInterval = 0
blockStatus.isRunning = true
}
if blockStatus.isRunning {
... rest of your code
}
This has the added advantage of not crashing if there are no children nodes. It just won't enter the block.

Resources