Skip to content

Commit

Permalink
Code cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
cellularmitosis committed Jan 15, 2021
1 parent 3e6634e commit 26aed2c
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 90 deletions.
163 changes: 91 additions & 72 deletions GridNotes/GridNotes/GridKeyboardViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,17 @@
import UIKit


/// The main piano view controller.
class GridKeyboardViewController: UIViewController {

var rows: [KeyRowView] = []
var toolbar: UIToolbar!

enum KeysPerOctave: String, CaseIterable {
case chromaticKeys
case diatonicKeys

var name: String {
switch self {
case .chromaticKeys:
return "Chromatic (all keys)"
return "Chromatic (all 12 keys)"
case .diatonicKeys:
return "Diatonic (only in-scale keys)"
}
Expand Down Expand Up @@ -82,54 +80,68 @@ class GridKeyboardViewController: UIViewController {

func set(model: Model) {
self.model = model
_configureToolbar()
_configureKeyRows()
if isViewLoaded {
_reconfigureToolbar()
_reconfigureKeyRows()
}
}

// MARK: - Internals

private var _rows: [KeyRowView] = []
private var _toolbar: UIToolbar!

override func viewDidLoad() {
super.viewDidLoad()

func assembleViewHierarchy() {
toolbar.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(toolbar)
_toolbar.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(_toolbar)

for row in rows {
for row in _rows {
row.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(row)
}

view.topAnchor.constraint(equalTo: toolbar.topAnchor).isActive = true
view.leadingAnchor.constraint(equalTo: toolbar.leadingAnchor).isActive = true
view.trailingAnchor.constraint(equalTo: toolbar.trailingAnchor).isActive = true
toolbar.bottomAnchor.constraint(equalTo: rows.first!.topAnchor).isActive = true
toolbar.heightAnchor.constraint(equalToConstant: 44).isActive = true
// toolbar height.
_toolbar.heightAnchor.constraint(equalToConstant: 44).isActive = true

// pin the toolbar to the top and sides.
view.topAnchor.constraint(equalTo: _toolbar.topAnchor).isActive = true
view.leadingAnchor.constraint(equalTo: _toolbar.leadingAnchor).isActive = true
view.trailingAnchor.constraint(equalTo: _toolbar.trailingAnchor).isActive = true

// pin the toolbar bottom the the first row of keys.
_toolbar.bottomAnchor.constraint(equalTo: _rows.first!.topAnchor).isActive = true

// stack the key rows vertically.
for i in 0..<(model.octaves.count-1) {
rows[i].bottomAnchor.constraint(equalTo: rows[i+1].topAnchor).isActive = true
_rows[i].bottomAnchor.constraint(equalTo: _rows[i+1].topAnchor).isActive = true
}

for i in 1..<model.octaves.count {
rows[i].heightAnchor.constraint(equalTo: rows[0].heightAnchor).isActive = true
// set the key rows to have equal heights.
for row in _rows.dropFirst() {
row.heightAnchor.constraint(equalTo: _rows.first!.heightAnchor).isActive = true
}

let guide = view.layoutMarginsGuide

switch UIDevice.current.userInterfaceIdiom {
case .phone:
for row in rows {
// on iPhone, pin the key rows all the way to the edges of the screen.
for row in _rows {
view.leadingAnchor.constraint(equalTo: row.leadingAnchor).isActive = true
view.trailingAnchor.constraint(equalTo: row.trailingAnchor).isActive = true
}
view.bottomAnchor.constraint(equalTo: rows.last!.bottomAnchor).isActive = true
view.bottomAnchor.constraint(equalTo: _rows.last!.bottomAnchor).isActive = true
case .pad:
for row in rows {
// on iPad, pin the key rows to the layout margin guide.
for row in _rows {
guide.leadingAnchor.constraint(equalTo: row.leadingAnchor).isActive = true
guide.trailingAnchor.constraint(equalTo: row.trailingAnchor).isActive = true
}
guide.bottomAnchor.constraint(
equalToSystemSpacingBelow: rows.last!.bottomAnchor,
equalToSystemSpacingBelow: _rows.last!.bottomAnchor,
multiplier: 2)
.isActive = true
default:
Expand All @@ -138,21 +150,22 @@ class GridKeyboardViewController: UIViewController {
}

for _ in model.octaves {
rows.append(KeyRowView())
_rows.append(KeyRowView())
}

view.backgroundColor = UIColor.white

// Using UIToolbar.init() results in constraint conflicts, so instead we init with a frame.
toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: view.bounds.width, height: 44))
_toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: view.bounds.width, height: 44))

assembleViewHierarchy()
_configureToolbar()
_configureKeyRows()
_reconfigureToolbar()
_reconfigureKeyRows()
}

private func _configureToolbar() {
toolbar.barTintColor = UIColor.white
/// (Re)Populate the toolbar with labels and buttons according to our model.
private func _reconfigureToolbar() {
_toolbar.barTintColor = UIColor.white
var items = [UIBarButtonItem]()

let titleItem = UIBarButtonItem.init(
Expand Down Expand Up @@ -193,61 +206,56 @@ class GridKeyboardViewController: UIViewController {
)
)

toolbar.setItems(items, animated: false)
_toolbar.setItems(items, animated: false)
}

@objc func didPressClear() {
for note in model.stuckKeys {
keyDidGetReleased(absoluteNote: note)
}
_configureKeyRows()
}

private func _configureKeyRows() {
self.model.stuckKeys = []
stopPlayingAllNotes()

for (i, octave) in model.octaves.reversed().enumerated() {
_configureKeyRow(index: i, octave: octave)
}
for row in rows {
row.delegate = self
}
}

private func _configureKeyRow(index: Int, octave: Octave) {
let firstNote = AbsoluteNote(note: model.tonicNote, octave: octave)
let allNotes = AbsoluteNote.chromaticScale(from: firstNote)
let scaleIndices = model.scale.semitoneIndices
/// (Re)Configure the key rows according to our model.
private func _reconfigureKeyRows() {

func reconfigureKeyRow(index: Int, octave: Octave) {
let firstNote = AbsoluteNote(note: model.tonicNote, octave: octave)
let allNotes = AbsoluteNote.chromaticScale(from: firstNote)
let scaleIndices = model.scale.semitoneIndices

let styledNotes: [(AbsoluteNote, KeyRowView.KeyStyle)?]
switch model.keysPerOctave {
let styledNotes: [(AbsoluteNote, KeyRowView.KeyStyle)?]
switch model.keysPerOctave {

case .chromaticKeys:
styledNotes = allNotes.enumerated().map { (index, note) in
if let note = note {
if scaleIndices.contains(index) {
return (note, .normal)
case .chromaticKeys:
styledNotes = allNotes.enumerated().map { (index, note) in
if let note = note {
if scaleIndices.contains(index) {
return (note, .normal)
} else {
let keyStyle = KeyRowView.KeyStyle(rawValue: model.nonScaleStyle.rawValue)!
return (note, keyStyle)
}
} else {
let keyStyle = KeyRowView.KeyStyle(rawValue: model.nonScaleStyle.rawValue)!
return (note, keyStyle)
return nil
}
} else {
return nil

}

}
case .diatonicKeys:
styledNotes = model.scale.absoluteNotes(fromTonic: firstNote).map { note in
guard let note = note else { return nil }
return (note, KeyRowView.KeyStyle.normal)
}

case .diatonicKeys:
styledNotes = model.scale.absoluteNotes(fromTonic: firstNote).map { note in
guard let note = note else { return nil }
return (note, KeyRowView.KeyStyle.normal)
}

let rowModel = KeyRowView.Model(styledNotes: styledNotes, stickyKeys: model.stickyKeys)
_rows[index].set(model: rowModel)
}

self.model.stuckKeys = []
stopPlayingAllNotes()

let rowModel = KeyRowView.Model(styledNotes: styledNotes, stickyKeys: model.stickyKeys)
rows[index].set(model: rowModel)
for (i, octave) in model.octaves.reversed().enumerated() {
reconfigureKeyRow(index: i, octave: octave)
}
for row in _rows {
row.delegate = self
}
}

// Allow button presses to register near the edges of the screen.
Expand All @@ -261,14 +269,17 @@ class GridKeyboardViewController: UIViewController {

// MARK: - Target/Action

/// Action to present the settings screen.
@objc func didPressSettings() {
let settingsVC = SettingsViewController()
settingsVC.model = model
settingsVC.set(model: model)

settingsVC.modelDidChange = { [weak self] model in
guard let self = self else { return }
self.set(model: model)
self.dismissSettings()
}

settingsVC.navigationItem.rightBarButtonItem = UIBarButtonItem(
barButtonSystemItem: .done,
target: self,
Expand All @@ -281,6 +292,14 @@ class GridKeyboardViewController: UIViewController {
@objc func dismissSettings() {
dismiss(animated: true, completion: nil)
}

/// Action for the button which clears all of the stuck keys.
@objc func didPressClear() {
for note in model.stuckKeys {
keyDidGetReleased(absoluteNote: note)
}
_reconfigureKeyRows()
}
}

extension GridKeyboardViewController: KeyRowDelegate {
Expand All @@ -290,15 +309,15 @@ extension GridKeyboardViewController: KeyRowDelegate {
if model.stickyKeys {
model.stuckKeys.insert(absoluteNote)
}
_configureToolbar()
_reconfigureToolbar()
}

func keyDidGetReleased(absoluteNote: AbsoluteNote) {
stopPlaying(absoluteNote: absoluteNote)
if model.stickyKeys {
model.stuckKeys.remove(absoluteNote)
}
_configureToolbar()
_reconfigureToolbar()
}
}

Expand Down
36 changes: 22 additions & 14 deletions GridNotes/GridNotes/KeyRowView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@ class KeyRowView: UIView {

var delegate: KeyRowDelegate? = nil

var keys: [UIButton] = []

override init(frame: CGRect) {
super.init(frame: frame)
translatesAutoresizingMaskIntoConstraints = false
Expand All @@ -48,42 +46,52 @@ class KeyRowView: UIView {
// MARK: - Internals

private static let _shadedGray: UIColor = UIColor(white: 0.85, alpha: 1)

private var _keys: [UIButton] = []

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

private var _hasSetUpConstraints: Bool = false
override func updateConstraints() {
super.updateConstraints()
if !_hasSetUpConstraints {
_hasSetUpConstraints = true

for k in keys {
// pin each key to the top and bottom of the row.
for k in _keys {
topAnchor.constraint(equalTo: k.topAnchor).isActive = true
bottomAnchor.constraint(equalTo: k.bottomAnchor).isActive = true
}

leadingAnchor.constraint(equalTo: keys.first!.leadingAnchor).isActive = true
for i in 0..<(keys.count-1) {
keys[i+1].leadingAnchor.constraint(equalTo: keys[i].trailingAnchor).isActive = true
// pin the first key's leading edge to the leading edge of the row.
leadingAnchor.constraint(equalTo: _keys.first!.leadingAnchor).isActive = true

// stack the keys horizontally.
for i in 0..<(_keys.count-1) {
_keys[i+1].leadingAnchor.constraint(equalTo: _keys[i].trailingAnchor).isActive = true
}

if UIDevice.current.userInterfaceIdiom == .pad, keys.count < 12 {
keys.first!.widthAnchor.constraint(
if UIDevice.current.userInterfaceIdiom == .pad, _keys.count < 12 {
// On iPad, when using less than 12 keys, don't expand the row to full width (use 1/12th per key).
_keys.first!.widthAnchor.constraint(
equalTo: widthAnchor,
multiplier: 1.0 / 12.0
).isActive = true
} else {
trailingAnchor.constraint(equalTo: keys.last!.trailingAnchor).isActive = true
// otherwise, pin the last key's trailing edge to the row's trailing edge.
trailingAnchor.constraint(equalTo: _keys.last!.trailingAnchor).isActive = true
}

for k in keys.dropFirst() {
keys.first!.widthAnchor.constraint(equalTo: k.widthAnchor).isActive = true
// set the keys to have equal widths.
for k in _keys.dropFirst() {
_keys.first!.widthAnchor.constraint(equalTo: k.widthAnchor).isActive = true
}
}
}
private var _hasSetUpConstraints: Bool = false

/// (Re)Construct the views according to the model.
private func _reloadViews() {

func buttonText(absoluteNote: AbsoluteNote) -> String {
Expand All @@ -108,7 +116,7 @@ class KeyRowView: UIView {
for subview in subviews {
subview.removeFromSuperview()
}
keys.removeAll()
_keys.removeAll()

for (i, styledNote) in model.styledNotes.enumerated() {
let key = UIButton(type: .system)
Expand Down Expand Up @@ -152,7 +160,7 @@ class KeyRowView: UIView {
key.backgroundColor = KeyRowView._shadedGray
}

keys.append(key)
_keys.append(key)
}

_hasSetUpConstraints = false
Expand Down
Loading

0 comments on commit 26aed2c

Please sign in to comment.