Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save marcpalmer/984048536937eb332851e87c3f862728 to your computer and use it in GitHub Desktop.
Save marcpalmer/984048536937eb332851e87c3f862728 to your computer and use it in GitHub Desktop.
An example of how you might iterate over a bunch of discrete features that provide similar functionality, such as photo filters
// Demo of iterating over subfeatures that all have a similar action and possibly performing that action.
/// Type-eraser for the action type so we can perform it irresepective of action type
struct AnyConditionalActionBinding<InputType, PresenterType> {
let _perform: (_ input: InputType, _ presenter: PresenterType) -> Bool
init<FeatureType, ActionType>(_ actionBinding: ConditionalActionBinding<FeatureType, ActionType>) where ActionType.InputType == InputType, ActionType.PresenterType == PresenterType {
_perform = { (_ input: InputType, _ presenter: PresenterType) -> Bool in
if let request = actionBinding.request() {
request.perform(input: input, presenter: presenter)
return true
} else {
return false
}
}
}
/// Call the closure we kept that we know accepts the args we receive here, without knowing the type
func performIfPossible(input: InputType, presenter: PresenterType) -> Bool {
return _perform(input, presenter)
}
}
/// This is whatever the filter actions need in order to perform
class ImageInfo: FlintLoggable {
/// let sourceImage: UIImage
}
/// This is whatever is needed to present the filter actions UI
protocol ImagePresenter {
}
/// The app's filter features group
class FiltersFeature: FeatureGroup {
static var subfeatures: [FeatureDefinition.Type] = [
SolarizeFilterFeature.self,
NoirFilterFeature.self
]
}
protocol ImageFilterAction: Action where InputType: ImageInfo {
}
/// The protocol defining the way to uniformly access the apply action across any filter feature.
/// Filter features would have to conform to this instead of ConditionalFeature
protocol FilterFeature: ConditionalFeature {
/// We use this to access the type-erased apply action that takes specific arg types we define here
static var applyAction: AnyConditionalActionBinding<ImageInfo, ImagePresenter> { get }
}
/// An example solarize filter feature
final class SolarizeFilterFeature: FilterFeature {
typealias ApplyActionType = ApplySolarizeFilterAction
static let description: String = "Solarize Filter"
static let apply = action(ApplySolarizeFilterAction.self)
static var applyAction: AnyConditionalActionBinding<ImageInfo, ImagePresenter> = AnyConditionalActionBinding(apply)
static func constraints(requirements: FeatureConstraintsBuilder) {
// IAP or whatevs
}
static func prepare(actions: FeatureActionsBuilder) {
actions.declare(apply)
}
}
/// An example noir filter feature
final class NoirFilterFeature: FilterFeature {
typealias ApplyActionType = ApplyNoirFilterAction
static let description: String = "Noir Filter"
static let apply = action(ApplyNoirFilterAction.self)
static var applyAction: AnyConditionalActionBinding<ImageInfo, ImagePresenter> = AnyConditionalActionBinding(apply)
static func constraints(requirements: FeatureConstraintsBuilder) {
// IAP or whatevs
}
static func prepare(actions: FeatureActionsBuilder) {
actions.declare(apply)
}
}
/// An action to apply the solarize filter
final class ApplySolarizeFilterAction: ImageFilterAction {
typealias InputType = ImageInfo
typealias PresenterType = ImagePresenter
static func perform(context: ActionContext<ImageInfo>, presenter: ImagePresenter, completion: @escaping (ActionPerformOutcome) -> Void) {
// Do fancy filter stuff
}
}
/// An action to apply the noir filter
final class ApplyNoirFilterAction: ImageFilterAction {
typealias InputType = ImageInfo
typealias PresenterType = ImagePresenter
static func perform(context: ActionContext<ImageInfo>, presenter: ImagePresenter, completion: @escaping (ActionPerformOutcome) -> Void) {
// Do fancy filter stuff
}
}
/// A stub so we can fake calling the actions
class FakeImagePresenter: ImagePresenter { }
extension FeatureDefinition {
static var applyFilterWorkaround: AnyConditionalActionBinding<ImageInfo, ImagePresenter>? { return nil }
}
extension FeatureDefinition where Self: FilterFeature {
static var applyFilterWorkaround: AnyConditionalActionBinding<ImageInfo, ImagePresenter>? { return applyAction }
}
/// An example of how you'd iterate over these filter features and perform them
class FilterEnumeratorLogic {
func test() {
var filterInfos: [String:AnyConditionalActionBinding<ImageInfo, ImagePresenter>] = [:]
for feature in FiltersFeature.subfeatures {
guard let applyAction = feature.applyFilterWorkaround else {
fatalError("Can only have filter subfeatures")
}
print("Filter: \(feature.description)")
filterInfos[feature.description] = applyAction
}
// Show the filters, but we'll just pretend we choose one
let selectedFilter = filterInfos.first!
let filterActionToApply = selectedFilter.value
let possible = filterActionToApply.performIfPossible(input: ImageInfo(), presenter: FakeImagePresenter())
print("Filter \(selectedFilter.key) was permitted: \(possible)")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment