Skip to content

Commit

Permalink
Merge branch 'release/1.0.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
maximkhatskevich committed May 9, 2017
2 parents 13aacba + ea9466b commit 3e4f272
Show file tree
Hide file tree
Showing 12 changed files with 115 additions and 83 deletions.
4 changes: 2 additions & 2 deletions Info/App.plist
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0-WIP</string>
<string>1.0.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>0</string>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
Expand Down
4 changes: 2 additions & 2 deletions Info/Tst.plist
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<string>1.0.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>0</string>
<string>1</string>
</dict>
</plist>
55 changes: 26 additions & 29 deletions Main.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
4A823B2678C4C39744B1121D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 07E03CB7ED7B82730C436137 /* LaunchScreen.storyboard */; };
4F1935479D0BC1B6A77CFF48 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 019403469F3B217B4F4FB317 /* AppDelegate.swift */; };
940B1CF11EC17A590073D729 /* Tst.M.Core.swift in Sources */ = {isa = PBXBuildFile; fileRef = 940B1CF01EC17A590073D729 /* Tst.M.Core.swift */; };
940B1CF71EC1B2600073D729 /* M.Core.swift in Sources */ = {isa = PBXBuildFile; fileRef = 940B1CF31EC1B2600073D729 /* M.Core.swift */; };
940B1CF81EC1B2600073D729 /* M.Intersection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 940B1CF41EC1B2600073D729 /* M.Intersection.swift */; };
940B1CF91EC1B2600073D729 /* M.swift in Sources */ = {isa = PBXBuildFile; fileRef = 940B1CF51EC1B2600073D729 /* M.swift */; };
940B1CFA1EC1B2600073D729 /* M.TrafficLight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 940B1CF61EC1B2600073D729 /* M.TrafficLight.swift */; };
94102D7E1EBF0FCC00A9C45B /* V.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94102D7D1EBF0FCC00A9C45B /* V.swift */; };
94102D801EBF10BD00A9C45B /* V.Delegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94102D7F1EBF10BD00A9C45B /* V.Delegate.swift */; };
94102D821EBF117E00A9C45B /* V.Window.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94102D811EBF117E00A9C45B /* V.Window.swift */; };
Expand All @@ -21,11 +25,6 @@
94102D8C1EBF159500A9C45B /* V.Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94102D8B1EBF159500A9C45B /* V.Defaults.swift */; };
94102D8E1EBF176A00A9C45B /* V.Root.View.binded.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94102D8D1EBF176A00A9C45B /* V.Root.View.binded.swift */; };
94102D901EBF17EA00A9C45B /* V.Root.View.initialized.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94102D8F1EBF17EA00A9C45B /* V.Root.View.initialized.swift */; };
94102D931EBF1A6F00A9C45B /* M.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94102D921EBF1A6F00A9C45B /* M.swift */; };
94102D951EBF1AF600A9C45B /* M.Core.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94102D941EBF1AF600A9C45B /* M.Core.swift */; };
94102D971EBFE35500A9C45B /* M.TrafficLight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94102D961EBFE35500A9C45B /* M.TrafficLight.swift */; };
94102D991EBFE3F700A9C45B /* M.Intersection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94102D981EBFE3F700A9C45B /* M.Intersection.swift */; };
94102D9D1EC0288200A9C45B /* Preview.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 94102D9C1EC0288200A9C45B /* Preview.storyboard */; };
94102D9F1EC0296400A9C45B /* V.Root.View.IBSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94102D9E1EC0296400A9C45B /* V.Root.View.IBSupport.swift */; };
94102DA11EC02B0100A9C45B /* V.Root.View.awaiting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94102DA01EC02B0100A9C45B /* V.Root.View.awaiting.swift */; };
94102DA31EC02B5D00A9C45B /* V.Root.View.running.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94102DA21EC02B5D00A9C45B /* V.Root.View.running.swift */; };
Expand Down Expand Up @@ -80,6 +79,10 @@
83BD7610D073BEC15EF39D0D /* Pods_App.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App.framework; sourceTree = BUILT_PRODUCTS_DIR; };
84C97AF44F4D4367ED2FE77D /* TrafficLights.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TrafficLights.app; sourceTree = BUILT_PRODUCTS_DIR; };
940B1CF01EC17A590073D729 /* Tst.M.Core.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Tst.M.Core.swift; sourceTree = "<group>"; };
940B1CF31EC1B2600073D729 /* M.Core.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = M.Core.swift; sourceTree = "<group>"; };
940B1CF41EC1B2600073D729 /* M.Intersection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = M.Intersection.swift; sourceTree = "<group>"; };
940B1CF51EC1B2600073D729 /* M.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = M.swift; sourceTree = "<group>"; };
940B1CF61EC1B2600073D729 /* M.TrafficLight.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = M.TrafficLight.swift; sourceTree = "<group>"; };
94102D7D1EBF0FCC00A9C45B /* V.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = V.swift; sourceTree = "<group>"; };
94102D7F1EBF10BD00A9C45B /* V.Delegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = V.Delegate.swift; sourceTree = "<group>"; };
94102D811EBF117E00A9C45B /* V.Window.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = V.Window.swift; sourceTree = "<group>"; };
Expand All @@ -89,10 +92,6 @@
94102D8B1EBF159500A9C45B /* V.Defaults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = V.Defaults.swift; sourceTree = "<group>"; };
94102D8D1EBF176A00A9C45B /* V.Root.View.binded.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = V.Root.View.binded.swift; sourceTree = "<group>"; };
94102D8F1EBF17EA00A9C45B /* V.Root.View.initialized.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = V.Root.View.initialized.swift; sourceTree = "<group>"; };
94102D921EBF1A6F00A9C45B /* M.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = M.swift; sourceTree = "<group>"; };
94102D941EBF1AF600A9C45B /* M.Core.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = M.Core.swift; sourceTree = "<group>"; };
94102D961EBFE35500A9C45B /* M.TrafficLight.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = M.TrafficLight.swift; sourceTree = "<group>"; };
94102D981EBFE3F700A9C45B /* M.Intersection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = M.Intersection.swift; sourceTree = "<group>"; };
94102D9C1EC0288200A9C45B /* Preview.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Preview.storyboard; sourceTree = "<group>"; };
94102D9E1EC0296400A9C45B /* V.Root.View.IBSupport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = V.Root.View.IBSupport.swift; sourceTree = "<group>"; };
94102DA01EC02B0100A9C45B /* V.Root.View.awaiting.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = V.Root.View.awaiting.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -138,7 +137,7 @@
isa = PBXGroup;
children = (
019403469F3B217B4F4FB317 /* AppDelegate.swift */,
94102D911EBF1A6600A9C45B /* Model */,
940B1CF21EC1B2600073D729 /* Model */,
94102D7C1EBF0FB000A9C45B /* View */,
07E03CB7ED7B82730C436137 /* LaunchScreen.storyboard */,
);
Expand Down Expand Up @@ -184,6 +183,17 @@
);
sourceTree = "<group>";
};
940B1CF21EC1B2600073D729 /* Model */ = {
isa = PBXGroup;
children = (
940B1CF51EC1B2600073D729 /* M.swift */,
940B1CF31EC1B2600073D729 /* M.Core.swift */,
940B1CF61EC1B2600073D729 /* M.TrafficLight.swift */,
940B1CF41EC1B2600073D729 /* M.Intersection.swift */,
);
path = Model;
sourceTree = "<group>";
};
94102D7C1EBF0FB000A9C45B /* View */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -220,18 +230,6 @@
path = View;
sourceTree = "<group>";
};
94102D911EBF1A6600A9C45B /* Model */ = {
isa = PBXGroup;
children = (
94102D921EBF1A6F00A9C45B /* M.swift */,
94102D941EBF1AF600A9C45B /* M.Core.swift */,
94102D961EBFE35500A9C45B /* M.TrafficLight.swift */,
94102D981EBFE3F700A9C45B /* M.Intersection.swift */,
);
name = Model;
path = View/Root/Model;
sourceTree = "<group>";
};
BE458BB5A533BF01E4DB7ABB /* Frameworks */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -354,7 +352,6 @@
files = (
EF3718CAD6FC9FF3C5692D48 /* Main.xcassets in Resources */,
4A823B2678C4C39744B1121D /* LaunchScreen.storyboard in Resources */,
94102D9D1EC0288200A9C45B /* Preview.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -482,20 +479,20 @@
buildActionMask = 2147483647;
files = (
D62E0DE9025BD06F38F3F4C5 /* R.generated.swift in Sources */,
94102D931EBF1A6F00A9C45B /* M.swift in Sources */,
940B1CF81EC1B2600073D729 /* M.Intersection.swift in Sources */,
94102DA11EC02B0100A9C45B /* V.Root.View.awaiting.swift in Sources */,
94102D951EBF1AF600A9C45B /* M.Core.swift in Sources */,
94102D991EBFE3F700A9C45B /* M.Intersection.swift in Sources */,
94102D971EBFE35500A9C45B /* M.TrafficLight.swift in Sources */,
94102D7E1EBF0FCC00A9C45B /* V.swift in Sources */,
94102D8C1EBF159500A9C45B /* V.Defaults.swift in Sources */,
94102D801EBF10BD00A9C45B /* V.Delegate.swift in Sources */,
4F1935479D0BC1B6A77CFF48 /* AppDelegate.swift in Sources */,
94102D821EBF117E00A9C45B /* V.Window.swift in Sources */,
940B1CF91EC1B2600073D729 /* M.swift in Sources */,
940B1CFA1EC1B2600073D729 /* M.TrafficLight.swift in Sources */,
94102D871EBF12B200A9C45B /* V.Root.swift in Sources */,
94102D851EBF129200A9C45B /* V.Root.Ctrl.swift in Sources */,
94102D8A1EBF13CF00A9C45B /* V.Root.View.swift in Sources */,
94102DA31EC02B5D00A9C45B /* V.Root.View.running.swift in Sources */,
940B1CF71EC1B2600073D729 /* M.Core.swift in Sources */,
94102D9F1EC0296400A9C45B /* V.Root.View.IBSupport.swift in Sources */,
94102D901EBF17EA00A9C45B /* V.Root.View.initialized.swift in Sources */,
94102D8E1EBF176A00A9C45B /* V.Root.View.binded.swift in Sources */,
Expand Down Expand Up @@ -573,7 +570,7 @@
CODE_SIGN_IDENTITY = "-";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 0;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
Expand Down Expand Up @@ -636,7 +633,7 @@
CODE_SIGN_IDENTITY = "-";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 0;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
Expand Down
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
![Platforms](https://img.shields.io/badge/platforms-ios-lightgrey.svg)
[![GitHub tag](https://img.shields.io/github/tag/maximkhatskevich/TrafficLights.svg)](https://github.com/maximkhatskevich/TrafficLights/releases)
![Swift version](https://img.shields.io/badge/swift-3-orange.svg)

# Introduction

A single-view iOS app that simulates a set of traffic lights at an intersection.
Expand All @@ -18,4 +22,27 @@ The app should be written using Swift 3. Any external libraries should be includ

# Approach

[ProjectGenerator](https://xcessentials.github.io/ProjectGenerator/) and [Struct](https://github.com/workshop/struct) are used to manage Xcode project file, that makes it easy to merge conflicting changes on project settings and/or new/renamed files in the project. [Carthage](https://github.com/Carthage/Carthage) is being used to install 'ProjectGenerator' source files into the project.

[Fastlane](https://fastlane.tools) is being used for routine tasks automation, such as generate/regenerate project file and increment project version/build number.

[CocoaPods](https://cocoapods.org) helps managing dependencies, all dependencies are stored in the repo, so the app codebase is ready to build and run out of the box.

[R.swift](https://github.com/mac-cain13/R.swift) is used to improve development experience and eliminate errors when working with resources.

To see the source code - open the **TrafficLights.xcworkspace** file.

The app is built using the "unidirectional data flow" and "finite state machine" design patterns.

High level app architecture is implemented using [UniFlow](http://xcessentials.github.io/UniFlow/) framework. See [introduction](https://github.com/XCEssentials/UniFlow#pre-existing-solutions), [theoretical fundamentals](https://github.com/XCEssentials/UniFlow#theoretical-fundamentals)
and [methodology overview](https://github.com/XCEssentials/UniFlow#methodology-overview) to better understand how app data model and business logic are organized.

All source files are grouped into 2 big groups - **Model** (that represents data model and business logic) and **View** (that implements presentation logic and is responsigle for passing user input to the *Model* layer). The *View* types rely on *Model* types, while *Model* types know nothing about *View*.

All *Model* files start with `M.` prefix, while all *View* - with `V.` prefix. `AppDelegate.swift` file supposed to be part of `V.Delegate` file inside `View` folder/group, but kept with traditional name and at the top level to help developers who are unfamiliar with such naming conventions to find initial entry point faster.

The `Preview.storyboard` is not included in the app bundle, because it's only needed for developer convenience to preview views during development in real time without running the app. To make it work, the `V_Root_View` class is named this way and is not nested (ti is supposed to be nested inside `V.Root` type and have ful name like this: `V.Root.View`) due to limitations of IB Storyboards - nested types are not supported for view custom class on storyboards.

To give the code better structure, Swift "nested type" feature is widely used. If a type is not supposed to be ever instantiated, it's declared as enum without any single case. Outer type works as scope for nested types.

[MKHState](https://github.com/maximkhatskevich/MKHState) framework is used to manage GUI states (*View* classes).
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ extension M

let params: Params

var tick: Timer
var tick: Timer?

var next: Timer?

var timeLeft: TimeInterval = 0.0 // until switch red <-> green

Expand Down Expand Up @@ -90,15 +92,12 @@ extension M.Intersection
Operating(
params: p,
tick: Timer.every(p.tick) { next{ tick() } },
next: Timer.after(p.green) { next{ yellowNorthSouth() } },
timeLeft: p.change,
northSouth: .green,
eastWest: .red
)
}

//===

Timer.after(p.green) { next{ yellowNorthSouth() } }
}
}

Expand Down Expand Up @@ -132,11 +131,9 @@ extension M.Intersection

$0.northSouth = .green
$0.eastWest = .red

$0.next = Timer.after($0.params.green) { next{ yellowNorthSouth() } }
}

//===

Timer.after(op.params.green) { next{ yellowNorthSouth() } }
}
}

Expand All @@ -149,11 +146,12 @@ extension M.Intersection

//===

mutate{ $0.northSouth = .yellow }

//===

Timer.after(op.params.yellow) { next{ greenEastWest() } }
mutate{

$0.northSouth = .yellow

$0.next = Timer.after($0.params.yellow) { next{ greenEastWest() } }
}
}
}

Expand All @@ -174,11 +172,9 @@ extension M.Intersection

$0.northSouth = .red
$0.eastWest = .green

$0.next = Timer.after($0.params.green) { next{ yellowEastWest() } }
}

//===

Timer.after(op.params.green) { next{ yellowEastWest() } }
}
}

Expand All @@ -191,11 +187,12 @@ extension M.Intersection

//===

mutate{ $0.eastWest = .yellow }

//===

Timer.after(op.params.yellow) { next{ greenNorthSouth() } }
mutate{

$0.eastWest = .yellow

$0.next = Timer.after($0.params.yellow) { next{ greenNorthSouth() } }
}
}
}

Expand All @@ -204,7 +201,8 @@ extension M.Intersection
{
return transition(from: Operating.self, into: Ready.self) { op, become, _ in

op.tick.invalidate()
op.tick?.invalidate()
op.next?.invalidate()

//===

Expand Down
File renamed without changes.
File renamed without changes.
1 change: 1 addition & 0 deletions Src/View/Root/View/V.Root.View.IBSupport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Foundation

//===

@IBDesignable
extension V_Root_View
{
override
Expand Down
21 changes: 20 additions & 1 deletion Src/View/Root/View/V.Root.View.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class V_Root_View: UIView, DispatcherInitializable

lazy
var state: StateCtrl<V_Root_View> =
StateCtrl(for: self, V.Default.viewAnim())
StateCtrl(for: self, V_Root_View.anim())

//===

Expand Down Expand Up @@ -93,4 +93,23 @@ extension V_Root_View
extension V_Root_View: DiscreteSystem
{
typealias St = State<V_Root_View>

//===

static
func anim() -> Transition<V_Root_View>
{
return { (v, m, c) in

v.status.alpha = 0.0

//===

m()

//===

V.Default.anim({ v.status.alpha = 1.0 }, c)
}
}
}
19 changes: 1 addition & 18 deletions Src/View/V.Defaults.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,9 @@ extension V
static
let anim: GenericTransition = {

UIView.animate(withDuration: 1.0,
UIView.animate(withDuration: 0.3,
animations: $0,
completion: $1)
}

static
func viewAnim<View: UIView>() -> Transition<View>
{
return { (v, m, c) in

v.alpha = 0.0

//===

m()

//===

V.Default.anim({ v.alpha = 1.0 }, c)
}
}
}
}
Loading

0 comments on commit 3e4f272

Please sign in to comment.