Merge overlapping CGRects - ios

If I have a class Button:
class Button: Hashable {
private let id: String
var name: String
var rect: CGRect
}
I also have an array of Button: var buttons: [Button], how would I merge every overlapping button in the buttons array? CGRect has an intersects() -> Bool and union() -> CGRect functions.
I tried this and was getting index out of range errors:
for i in 0..<buttons.count {
for j in (i+1)..<buttons.count {
if buttons[i].rect.intersects(buttons[j].rect) {
let secondRect = buttons[j].rect
let union = buttons[i].rect.union(secondRect)
buttons[i].rect = union
buttons.remove(at: j)
}
}
}

Swift for loop is static, which means that range of the index determined at the start. While buttons.count keeps decreasing when you remove a next item, both i and j are counting til the start buttons.count, that's why you get a crash.
There's no dynamic for in swift (c-like), that's why you have to use while instead:
var i = 0
while i < buttons.count {
var j = i + 1
while j < buttons.count {
if buttons[i].rect.intersects(buttons[j].rect) {
let secondRect = buttons[j].rect
let union = buttons[i].rect.union(secondRect)
buttons[i].rect = union
buttons.remove(at: j)
} else {
j += 1
}
}
i += 1
}

Related

How to remove character from odd index in Swift String

I am new to swift & I have to remove characters from odd index from a given String
I did try with following code
var myStringObject = "HelloTestString"
myStringObject.enumerated().filter({ index, char in !(char == "1" && index % 2 == 0) })
but I am unable to find the desired result string. Can you please guide me how to remove characters from odd index in String.
You can't filter by two filter types. So you have to move to the old approach.
var myStringObject = "HelloTestString"
var newString = ""
var index = 0
while index < myStringObject.count {
if index % 2 != 0 {
let firstIndex: String.Index = myStringObject.startIndex
let desiredChar: Character = myStringObject[myStringObject.index(firstIndex, offsetBy: index)]
newString = newString + "\(desiredChar)"
}
index = index + 1
}
print(newString) //elTsSrn
Keeping your starting solution:
let couples = myStringObject.enumerated().filter { (arg0) -> Bool in
let (offset, _) = arg0
return offset % 2 == 0
}
print(couples)
or
let couples = myStringObject.enumerated().filter { (offset, _) -> Bool in
return offset % 2 == 0
} //Which is more similar to last version
You have then, an array of Tuples where first element is the offset, and the second the element.
$>[(offset: 0, element: "H"), (offset: 2, element: "l"), (offset: 4, element: "o"), (offset: 6, element: "e"), (offset: 8, element: "t"), (offset: 10, element: "t"), (offset: 12, element: "i"), (offset: 14, element: "g")]
Let's keep only the letters (and back to String, not a String.Element):
let onlyletters = couples.map({ String($0.element) })
Let's get it back into a String.
let result = onlyletters.joined()
In one line:
let oneLine = myStringObject.enumerated().filter({ $0.0 % 2 == 0 }).map({ String($0.element) }).joined()
You can create an auxiliary index and use defer to increase it after each iteration on your collection, this way you don't need to enumerate your string:
let string = "HelloTestString"
var index = 0
let filtered = string.filter { _ in
defer { index += 1 }
return index % 2 == 1
}
print(filtered) // "elTsSrn"
If you need to mutate your original string you can use removeAll with the same approach:
var string = "HelloTestString"
var index = 0
string.removeAll { _ in
defer { index += 1 }
return index % 2 == 0
}
print(string)
Implementing your own method
extending RangeReplaceableCollection to filter or removeAll elements that are in odd or even positions:
extension RangeReplaceableCollection {
var oddIndicesElements: Self {
var position = 0
return filter { _ in
defer { position += 1 }
return position % 2 == 1
}
}
var evenIndicesElements: Self {
var position = 0
return filter { _ in
defer { position += 1 }
return position % 2 == 0
}
}
mutating func removeAllEvenIndicesElements() {
var position = 0
removeAll { _ in
defer { position += 1 }
return position % 2 == 0
}
}
mutating func removeAllOddIndicesElements() {
var position = 0
removeAll { _ in
defer { position += 1 }
return position % 2 == 1
}
}
}
var myStringObject = "HelloTestString"
print(myStringObject.oddIndicesElements) // "elTsSrn"
myStringObject.removeAllEvenIndicesElements()
print(myStringObject) // "elTsSrn"

Gradually and randomly visualize string

I am currently working on a simple program to gradually and randomly visualize a string in two iteration. Right now I have managed to get the first iteration but I'm not sure how to do the second one. If someone could give any example or advice I would be very grateful. My code looks like this:
let s = "Hello playground"
let factor = 0.25
let factor2 = 0.45
var n = s.filter({ $0 != " " }).count // # of non-space characters
var m = lrint(factor * Double(n)) // # of characters to display
let t = String(s.map { c -> Character in
if c == " " {
// Preserve space
return " "
} else if Int.random(in: 0..<n) < m {
// Replace
m -= 1
n -= 1
return c
} else {
// Keep
n -= 1
return "_"
}
})
print(t) // h_l__ _l_______d
To clarify, I want to use factor2 in the second iteration to print something that randomly add letters on top of t that looks something like this h_l_o pl_g_____d.
Replacing Characters
Starting from #MartinR's code, you should remember the indices that have been replaced. So, I am going to slightly change the code that replaces characters :
let s = "Hello playground"
let factor = 0.25
let factor2 = 0.45
var n = s.filter({ $0 != " " }).count // # of non-space characters
let nonSpaces = n
var m = lrint(factor * Double(n)) // # of characters to display
var indices = Array(s.indices)
var t = ""
for i in s.indices {
let c = s[i]
if c == " " {
// Preserve space
t.append(" ")
indices.removeAll(where: { $0 == i })
} else if Int.random(in: 0..<n) < m {
// Keep
m -= 1
n -= 1
t.append(c)
indices.removeAll(where: { $0 == i })
} else {
// Replace
n -= 1
t.append("_")
}
}
print(t) //For example: _e___ ______ou_d
Revealing Characters
In order to do that, we should calculate the number of characters that we want to reveal:
m = lrint((factor2 - factor) * Double(nonSpaces))
To pick three indices to reveal randomly, we shuffle indices and then replace the m first indices :
indices.shuffle()
var u = t
for i in 0..<m {
let index = indices[i]
u.replaceSubrange(index..<u.index(after: index), with: String(s[index]))
}
indices.removeSubrange(0..<m)
print(u) //For example: _e__o _l__g_ou_d
I wrote StringRevealer struct, that handle all revealing logic for you:
/// Hide all unicode letter characters as `_` symbol.
struct StringRevealer {
/// We need mapping between index of string character and his position in state array.
/// This struct represent one such record
private struct Symbol: Hashable {
let index: String.Index
let position: Int
}
private let originalString: String
private var currentState: [Character]
private let charactersCount: Int
private var revealed: Int
var revealedPercent: Double {
return Double(revealed) / Double(charactersCount)
}
private var unrevealedSymbols: Set<Symbol>
init(_ text: String) {
originalString = text
var state: [Character] = []
var symbols: [Symbol] = []
var count = 0
var index = originalString.startIndex
var i = 0
while index != originalString.endIndex {
let char = originalString[index]
if CharacterSet.letters.contains(char.unicodeScalars.first!) {
state.append("_")
symbols.append(Symbol(index: index, position: i))
count += 1
} else {
state.append(char)
}
index = originalString.index(after: index)
i += 1
}
currentState = state
charactersCount = count
revealed = 0
unrevealedSymbols = Set(symbols)
}
/// Current state of text. O(n) conplexity
func text() -> String {
return currentState.reduce(into: "") { $0.append($1) }
}
/// Reveal one random symbol in string
mutating func reveal() {
guard let symbol = unrevealedSymbols.randomElement() else { return }
unrevealedSymbols.remove(symbol)
currentState[symbol.position] = originalString[symbol.index]
revealed += 1
}
/// Reveal random symbols on string until `revealedPercent` > `percent`
mutating func reveal(until percent: Double) {
guard percent <= 1 else { return }
while revealedPercent < percent {
reveal()
}
}
}
var revealer = StringRevealer("Hello товарищ! 👋")
print(revealer.text())
print(revealer.revealedPercent)
for percent in [0.25, 0.45, 0.8] {
revealer.reveal(until: percent)
print(revealer.text())
print(revealer.revealedPercent)
}
It use CharacterSet.letters inside, so most of languages should be supported, emoji ignored and not-alphabetic characters as well.

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 build a 2D board using Swift?

I want to create a 2D game board (like a chess board) and each square has a value inside (Int = 1). This board has initially 9 squares (3x3).
Then I need a player that starts at the center of the bard and when I move the player to the edge of the board, it expands.
Example: The player reaches the [3,3] square and the board grows to a 4x4 board.
Also, when as the player moves it will change the value inside each square, from 0 to 1.
This is what I have so far
//Variables to get the player position and expand the board
let initialSize = 3
var newValueX = 0
var newValueY = 0
var newPlayerX = 0
var newPlayerY = 0
//Board
var board= [[Int]]()
struct Player {
var X = 0
var Y = 0
}
func expandBoard (){
for k in 0..<9{
for i in 0..<initialSize {
for j in 0..<initialSize {
array[k].append([i,j])
}
}
}
}
func movePlayer(){
var player = Player() //this goes to viewDidLoad
player.X = newPlayerX
player.Y = newPlayerY
}
An example of the pretended output:
Player moved to [0,3] and changed the square value from 1 to 0.
How can I do this correctly?
EDIT:
I have
class Array2D<T> {
var columns = 1//: Int
var rows = 1//: Int
var array: Array<T?>
init(columns: Int, rows: Int) {
self.columns = columns
self.rows = rows
array = Array<T?>(repeating: nil, count:rows * columns)
}
subscript(column: Int, row: Int) -> T? {
get {
return array[(row * columns) + column]
}
set(newValue) {
array[(row * columns) + column] = newValue
}
}
}
func move(PX: Int, PY: Int){
if (board[PX, PY] == true){
moveCount += 1
board[PX, PY] = false
}
if (PX == board.rows.magnitude){
board.rows += 1
}
if (PY == board.columns.magnitude){
board.columns += 1
}
}
And when I run i get "Thread 1: Fatal error: Index out of range" on line (return array[(row * columns) + column]) from the subscript.
Any Idea how can I fix this?
UICollectionView would be another simple choice if you don't want use the SpriteKit.
I would recommend using SpriteKit, which have an element called 'Tile Map' which meets your requirements, here is a tutorial that can help.

Getting EXC_BAD_ACCESS while the object exists

I'm developing a music sequencer with a standard piano roll type UI.
It was working well until I made some changes in the model side but it suddenly started to report EXC_BAD_ACCESS at (seemingly) unrelated part.
What's strange is all the necessary variables have their values properly and actually I can print values with po.
In my understanding, EXC_BAD_ACCESS happens when an object doesn't exist, so this seems quite strange.
My question is:
Is it common to EXC_BAD_ACCESS even the values are there?
If that's the case what is the possible situation to cause that?
Any suggestion is helpful. Thanks
[Below are the codes]
In my subclass of UICollectionViewLayout:
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
// note cells
let cv = self.collectionView as! YMPianoRollCollectionView;
let pianoRoll = cv.pianoRollViewController;
// Call the below func to get the indexes of the Note Objects included in the specified rect
let indexArray: Array<Int> = pianoRoll!.getNoteIndexes(inRect:rect, useOnlyStartTime: false);
var retArray : [UICollectionViewLayoutAttributes] = []
for i in indexArray {
if let _ = pianoRoll?.pattern.eventSequence[i] as? YMPatternEventNoteOn {
retArray.append( self.layoutAttributesForPatternEventInfo(i) )
}
}
return retArray
}
In my "piano roll" class which contains UICollectionView
func getNoteIndexes(inRect rect:CGRect, useOnlyStartTime: Bool) -> Array<Int> {
//
// Transform given values into musical values
//
let musicRange :YMMusicalValueRange = screenInfo.getMusicalRange(rect);
let startTime = musicRange.origin.time;
let endTime = musicRange.origin.time + musicRange.size.timeLength;
let lowNoteU = musicRange.origin.noteNumber;
let highNoteU = musicRange.origin.noteNumber + musicRange.size.numberOfNotes;
var retArray : [Int] = []
for i in 0..<pattern.eventSequence.count {
if let e = pattern.eventSequence[i] as? YMPatternEventNoteOn {
//
// Prepare ranges
//
let noteNo = e.noteNo; //<- App Crashes Here with BAD_ACCESS
let noteStTime = e.time;
let noteEnTime = e.time + e.duration;
let targetNoteRange = Range<Int>(uncheckedBounds: (lowNoteU, highNoteU));
let targetTimeRange = Range<Int64>(uncheckedBounds: (startTime, endTime))
let noteTimeRange = Range<Int64>(uncheckedBounds: (noteStTime, noteEnTime))
//
// Check the match
//
let noteMatches = targetNoteRange.contains(noteNo);
let timeMatches = useOnlyStartTime ? targetTimeRange.contains(noteStTime)
: targetTimeRange.overlaps(noteTimeRange)
if noteMatches && timeMatches {
retArray.append( i );
NSLog("XXX Found: note \(noteNo) at \(e.time)");
}
}
}
return retArray;
}
Error:- The object states when it crashed
EDIT
Here's the YMPatternEventNoteOn declaration
class YMPatternEvent : Codable, CustomDebugStringConvertible {
var time : YMSequenceTime = 0
// Some utility funcs follow
// ...
}
class YMPatternEventNoteOn : YMPatternEvent {
var noteNo : Int = 64
var velocity : Int = 127
var duration : YMSequenceTime = 480
var tempBendId : Int = 0;
var tempVibratoId : Int = 0;
var tempArpeggioId : Int = 0;
convenience init(time :YMSequenceTime, noteNo : Int, velocity: Int, duration: YMSequenceTime) {
self.init();
self.time = time;
self.noteNo = noteNo;
self.velocity = velocity;
self.duration = duration;
}
// Other methods follow
// ...
}
EDIT2
Note event is created by the user's action
//
// In YMPattern object
//
func insertNote(time:YMSequenceTime, noteNo:Int, velocity:Int, duration:YMSequenceTime) -> Int
{
let onEvent = YMPatternEventNoteOn(time: time, noteNo: noteNo, velocity: velocity, duration: duration);
let retIndex = insertEvent(onEvent);
return retIndex;
}
func insertEvent(_ event: YMPatternEvent) -> Int {
let atTime = event.time;
var retIndex : Int = 0;
if(eventSequence.count<1){
// If it's the first event just add it
eventSequence.append(event);
retIndex = 0;
} else {
// If any event already exists, insert with time order in consideration
var i : Int = 0;
while( atTime > eventSequence[i].time ){
i += 1;
if( i >= eventSequence.count ){
break;
}
}
retIndex = i;
eventSequence.insert(event, at: i)
}
}
//
// In pianoroll view controller
//
func actionButtonReleased(afterDragging: Bool) {
let values:YMMusicalValuePoint = screenInfo.getMusicalPosition(cursorPosition);
// insert new event with default velocity and duration
let _ = pattern.insertNote(time: values.time, noteNo: values.noteNumber, velocity: 127, duration: screenInfo.timeTicsPerDivision());
collectionView.reloadData();
}
I've solved it in a way.
By setting the optimization level to "Whole Module Optimization" it stopped reporting the error.
I don't know what is happening internally but if someone is having the same issue, this might work as a quick fix.

Resources