Giter Club home page Giter Club logo

spots's Introduction

Spots logo

Spots is a cross-platform view controller framework for building component-based UIs. The internal architecture is built using generic view models that can be transformed both to and from JSON. So, moving your UI declaration to a backend is as easy as pie. Data source and delegate setup is handled by Spots, so there is no need for you to do that manually. The public API is jam-packed with convenience methods for performing mutation, it is as easy as working with a regular collection type.

Table of Contents

Spots Icon

Getting started with Spots

If you are looking for a way to get started with Spots, we recommend taking a look at our Getting started guide.

Origin Story

We wrote a Medium article about how and why we built Spots. You can find it here: Hitting the sweet spot of inspiration

Universal support

Apple's definition of a universal applications is iPhone and iPad. Spots takes this a step further with one controller tailored to each platform to support all your UI related update needs. Internally, everything conforms to the same shared protocol. What this means for you, is that get a unified experience when developing for iOS, tvOS or macOS.

Usage

Use the following links to dive a bit deeper into how Spots works.

How does it work?

At the top level of Spots, you have the SpotsController which is the replacement for your view controller.

Inside of the SpotsController, you have a SpotsScrollView that handles the linear layout of the components that you add to your data source. It is also in charge of giving the user a unified scrolling experience. Scrolling is disabled on all underlaying components except for components that have horizontal scrolling.

So how does scrolling work? Whenever a user scrolls, the SpotsScrollView computes the offset and size of its children. By using this technique you can easily create screens that contain lists, grids and carousels with a scrolling experience as smooth as proverbial butter. By dynamically changing the size and offset of the children, SpotsScrollView also ensures that reusable views are allocated and deallocated like you would expect them to. SpotsScrollView uses KVO on any view that gets added so if one component changes height or position, the entire layout will invalidate itself and redraw it like it was intended.

SpotsController supports multiple Component's, each represent their own UI container and hold their own data source. Components all share the same data model called ComponentModel, it includes layout, interaction and view model data. Component gets its super-powers from protocol extensions, powers like mutation, layout processing and convenience methods for accessing model information.

Key features

  • JSON based views that could be served up by your backend.
  • Live editing.
  • View based caching for controllers, table and collection views.
  • Supports displaying multiple collections, tables and regular views in the same container.
  • Features both infinity scrolling and pull to refresh (on iOS), all you have to do is to setup delegates that conform to the public protocols on SpotsController.
  • No need to implement your own data source, every Component has its own set of Items, which is maintained internally and is there at your disposal if you decide to make changes to them.
  • Easy configuration for registering views. This improves code reuse and helps to theme your app and ultimately keep your application consistent.
  • A rich public API for appending, prepending, inserting, updating or deleting Items.
  • Has built-in support for regular views inside of both collection and table views. Write one view and use it across your application, when and where you want to use it.
  • Supports view states such as normal, highlighted and selected.
  • View height caching that improves performance as each view has its height stored as a calculated value. on the view model.
  • Supports multiple views inside the same data source, no more ugly if-statements in your implementation;
  • Soft & hard updates to UI components.
  • Supports both views made programmatically and nib-based views. Spots handles this for you by using a view registry.

Installation

Spots is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod 'Spots'

Spots is also available through Carthage. To install it, add the following to your Cartfile:

github "hyperoslo/Spots"

Changelog

Looking for a change log? You can find it here

Dependencies

  • Cache Used for ComponentModel and Item caching when initializing a SpotsController or CoreComponent object with a cache key.

Author

Hyper made this with ❤️. If you’re using this library we probably want to hire you! Send us an email at [email protected].

Contribute

We would love you to contribute to Spots, check the CONTRIBUTING file for more info.

Credits

License

Spots is available under the MIT license. See the LICENSE file for more info.

spots's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

spots's Issues

Internal component naming

I find it a bit confusing to take a step backwards and introduce a new naming convention for classes. Having everything share a spot prefix or suffix is a bit cumbersome and confusing at times. Maybe it would be better to stick to a convention that is more familiar to the platform or just more generic instead of forcing a convention that couples directly to the pod name.

What do you guys think?

@RamonGilabert @vadymmarkov

Simple example with a tableview containing multiple kinds of tableviewcells?

I am playing with this code in the playground. I want to have different kinds of tableview cells displayed. When I enter a value for kind, I get an error that the reuseidentifier is not registered. So here is the recourse I have taken: check the content type based on the meta information.

But this is obviously not the correct way of doing things. Ideally I should have different view models (with different meta info), but be able to generate different types of tableviewcells using the view model.kind.

I did go through your examples. But they appear to be designed to show the power of your framework. Can you provide a simple sample listing different kinds of cells?

Could you also document more clear-cut definitions of components, spots, ... It is a little hard to discern what the different entities are within the framework and what purpose they serve.

Thanks.

public class ListCell: UITableViewCell, SpotConfigurable {

    public var size = CGSize(width: 0, height: 60)
    public var item: ViewModel?

    lazy var selectedView = UIView().then {
        $0.backgroundColor = UIColor.blueColor().colorWithAlphaComponent(0.4)
    }

    public override init(style: UITableViewCellStyle, reuseIdentifier: String!) {
        super.init(style: .Subtitle, reuseIdentifier: reuseIdentifier)
        selectedBackgroundView = selectedView
    }

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

    public func configure(inout item: ViewModel) {
        backgroundColor = UIColor.whiteColor()
        textLabel?.textColor = UIColor.blackColor()
        detailTextLabel?.textColor = UIColor.blackColor()

        if let action = item.action where action.isPresent {
            accessoryType = .DisclosureIndicator
        } else {
            accessoryType = .None
        }

        if let contact = item.meta["contact"] as? Contact {
            textLabel?.text = contact.name
            detailTextLabel?.text = contact.rawPhones.first
        } else {
            textLabel?.text = item.title
            detailTextLabel?.text = item.subtitle
        }

        item.size.height = item.size.height > 0.0 ? item.size.height : size.height
    }

    public override func layoutSubviews() {
        super.layoutSubviews()

        textLabel?.x = 16
        detailTextLabel?.x = 16
    }
}

Improve composition

The composition feature is not very well tested and hence it is not optimal. When we tested this on tvOS, we quickly saw that composition is heavy reliant on the size. This causes problems when serving up view models from the backend. I think this needs to be improved by handling it internally if the size property is not set. I think we could do the same thing as we do for Spotable objects. If usesDynamicHeight is set to false (which is default), then Spots would computed all the sizes for all the items that are of composite type.

I think a good start would be to add more tests for this feature to check that it works correctly and then add another example to demonstrate the feature.

Add Travis

Now that we have tests, we should invite Travis CI to this repo.
Probably gonna regret this issue but what the heck!

Spots demo project fails to compile with Xcode 8.1

Got compile error when trying to run the SpotsDemo project.
"Command failed due to signal: Segmentation fault: 11"

Looks like Xcode 8.1 is unhappy with "Sugar" - particularly the Localization file.

dependencies-path /Users/navisingh/Library/Developer/Xcode/DerivedData/SpotsDemo-ccnfuqrrimqxvjecjmciykwssoyu/Build/Intermediates/Pods.build/Debug-iphonesimulator/Sugar.build/Objects-normal/x86_64/Localization.swiftdeps -o /Users/navisingh/Library/Developer/Xcode/DerivedData/SpotsDemo-ccnfuqrrimqxvjecjmciykwssoyu/Build/Intermediates/Pods.build/Debug-iphonesimulator/Sugar.build/Objects-normal/x86_64/Localization.o

0 swift 0x000000010903ca4d PrintStackTraceSignalHandler(void*) + 45
1 swift 0x000000010903c476 SignalHandler(int) + 470

Handle size change when presenting another view controller

There is a known issue in iOS that viewWillTransitionToSize is also called when the presented view controller is rotated, which causes unwanted size change handling in SpotsController

For now https://github.com/hyperoslo/Spots/blob/master/Sources/iOS/Classes/SpotsController.swift#L273, we 're checking for guard presentedViewController == nil else { return }. But there is an edge case when at the end of presented view controller dismissal, this checking is wrong

I think we need to rely on some other properties to check, instead of presentedViewController, or a custom flag. What do you think @hyperoslo/ios

Refactor `render()` into `view`.

I always found it weird and odd that render() is a function that returns the view for the Spotable object. I think it would be nicer and more natural if the Spotable object had a computed property called view.

This would very much be a breaking change but I well worth one in my opinion.
Maybe this could be something that we go for in 6.0.0.
We do have some other breaking changes that we would like to implement as well (see the issue about the protocols), and the naming convention of delegate methods.

Add generator script for registering Spots

Would it be neat if we added a script to generate a swift file with all the components that you have in your project so that you don't have to manually assign them in your SpotConfigurator for each project?

I see some drawbacks of it, that it will be tied to the class name in some way and be done via convention. So if the views name is ProfileGridItem, the identifier would look something like profile-grid-item. But maybe that is not a problem as they would always be unique.

What do you guys think? Do you see any disadvantages with that approach ?

Remove Sugar as a dependency

In Spots we only use a spares amount of all the functionality in Sugar so I'm thinking about removing it as a dependency. I guess the main thing that we use in Spots is the Grand Central Dispatch sugar, but if you think about it. You probably don't need all the features there either. Seeing that this is a UI related framework, I think that you would only need an interactive background thread and the main thread. When would you perform a background operation with low priority when it is UI related, never right?

Add lifecycle to Component

I think it would be handy to implement lifecycle methods on Component so that we could target and trigger operations based on that. We could also add delegate methods to give the developer more control over those operations. I think this could play a big role in how the CoreComponent objects are prepared and with some delegation you could introduce things like content operations more easily than you can do now.

func componentWillAppear(_ component: Component)
func componentDidAppear(_ component: Component)
func componentWillDisappear(_ component: Component)
func componentDidDisappear(_ component: Component)

This idea was sparked while me and @vadymmarkov were looking into React Native yesterday, in React, all component have their own lifecycle which can be incredibly powerful if done right.

What do you guys think? @hyperoslo/ios

Improve test coverage

Development has been a bit to wild-wild-west-ish for a while now, I think we need to add more tests to get better regression testing in the future. And to cover all the moving parts as well!

Please add Cartfile documentation.

One now needs to include:
Spots, Brick, Tailor, Cache, And CryptoSwift frameworks for carthage. Used to be just Spots, Brick, and Tailor.

UIScreen independent

I think it 's good that we can remove all references to UIScreen to get the size. Spots controller should depend on its parent controller to get the size. This also helps to support parent - child view controller relationship

How about carthage support?

I tried the following Cartfile:

github "hyperoslo/Spots" ~> 1.2.0
github "hyperoslo/Sugar" ~> 1.0.13
github "hyperoslo/Brick" ~> 0.4.0
github "zenangst/Tailor" ~> 0.10.0

But got the error:
"Failed to discover shared schemes in project Sugar.xcodeproj—either the project does not have any shared schemes, or xcodebuild never returned"

I can't use the pod because I can't use use_frameworks in my Podfile courtesy openSSL which is used in one of the pods in my pod file.

Improve didSelect method

When we implemented willDisplay and endDisplay, we included the view into the delegate method.
I think there is potential to include the view that was tapped in didSelect(item: Item, in spot: Spotable) as well.

So it would end up looking something like this:

func spotsDidSelect(view: SpotView, item: Item, in spot: Spotable) {

This would kind of make the method into a trio instead of having one of them standout for being less "cool".

What do you think? @hyperoslo/ios

Updated

I updated the example to include the framework name.

Unify implementation and get rid of core types

So I had this idea, what if we got rid of core types, like CarouselSpot, GridSpot etc. And instead the layout would configure the Spot to use what ever UI is appropriate based on kind. This way it would be easier to test as configuration would be handled by something else than the Spotable object. I'm pretty sure that this would reduce the amount of code duplication that we have right now with all the core types.

What this would mean is that we get rid of core types and use one polymorphic object type (preferably called Spot) that would render itself appropriately based on layout instructions.

Build failed for both cartfile and podspec

pod --version => 0.39.0

  1. in Spots/Examples/SpotsCards and use pod install
    Fetching podspec for Spots from ../../
    [!] Unable to find a specification for Sugar depended upon by Compass
  2. and using pod 'Spots', git: 'https://github.com/hyperoslo/Spots.git'. also give error
    [!] Unable to find a specification for Sugar depended upon by Compass
  3. also if creating my own dependencies it create error
    https://raw.githubusercontent.com/steve21124/Compass/master/Compass.podspec

Use constant values for sizes

Right now, we have hardcoded values for sizes, like

public class GridSpotCell: UICollectionViewCell, SpotConfigurable {

  /// The preferred view size for the view
  public var preferredViewSize = CGSize(width: 88, height: 88)
...
}

I think we can put them in a structs, not really for them being configurable, but make the size explicit. This also make the test less magic numbers, like expect(height).to.equal(count * Dimensions.gridCell.size.height)

version dependencies in the pod spec files.

Explicitly versioning the dependencies will avoid situations like we have currently where the pod is broken because one of the dependency (crypto swift) has moved on to swift 3.0.

Proposal: Unify animated/non-animated scrolling delegate APIs

Currently, different delegate methods are called in various places to notify the API users of scrolling events, depending on whether the scrolling was animated or not. Example in CarouselScrollDelegate:

func spotableCarouselDidEndScrolling(_ spot: Spotable, item: Item)
func spotableCarouselDidEndScrollingAnimated(_ spot: Spotable)

Having both of these methods both leads to inconsistency (the first one accepts an Item where the scond one doesn't), and makes the API slightly harder to implement (2 methods vs 1). The proposal is to unify both of these methods in all places where this pattern is used to instead become the following:

func spotableCarouselDidEndScrolling(_ spot: Spotable, item: Item, animated: Bool)

Which simply sends whether the event occured with an animation as a parameter.

Improve `ScrollDelegate`

I made separate issues for the other improvements to the API but it might be better to combine them with one for each protocol.

So for ScrollDelegate it would look something like this:

Before

func didReachBeginning(in scrollView: ScrollableView, completion: Completion)
func didReachEnd(in scrollView: ScrollableView, completion: Completion)

After

func spotsDidReachBeginning(in scrollView: ScrollableView, completion: Completion)
func spotsDidReachEnd(in scrollView: ScrollableView, completion: Completion)

SpotsNibDemo or just adding an empty xib to any project crash

Hi,

I'm getting this crash running the SpotsNibDemo or just adding even an empty .xib to any ios project that use Spots.

2016-11-28 21:08:53.201 SpotsNibDemo[484:13917030] *** Assertion failure in -[UINib initWithNibName:directory:bundle:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3600.5.2/UINib.m:94
2016-11-28 21:08:53.204 SpotsNibDemo[484:13917030] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid parameter not satisfying: (name != nil) && ([name length] > 0)'

The problem seams to be the super init call in the Controller init

super.init(nibName: nil, bundle: nil)

but I don't know how to pass here the right nibName.

Can you please help me with that?

Thank you so much.
Jacopo

I have an error when install Spots with Cartfile

I set in Cartfile
github "hyperoslo/Spots"

Error:
*** Building scheme "Spots-iOS" in Spots.xcodeproj
** BUILD FAILED **
...Carthage/Checkouts/Spots/Spots.xcodeproj -scheme Spots-iOS -configuration Release -sdk iphoneos ONLY_ACTIVE_ARCH=NO BITCODE_GENERATION_MODE=bitcode CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY= CARTHAGE=YES clean build) failed with exit code 65:
** BUILD FAILED **

Feature view model caching

It would be super nifty if Spots featured an internal caching mechanism, both on an entire controller and on a single spot. This would be entirely optional, it should default not to use it was it might not be needed for all views. So you could have an optional init method that instantiates a component or controller with a cache id, if components and view models exist, it will use those so that it doesn't need to wait for the network to fetch, parse and process everything into view model data.

I think that this could be a great and nifty improvement, Cache would be an ideal component for this use case.

Add label to `size` and `layout` to clarify what type should be passed.

We have some "legacy" methods that are still a bit unclear, things like setup(_ size) and layout(_ size). As @onmyway133 pointed out in a PR, these methods deserve some improvements.

Here are some suggestions on how to improve them:

Example 1

fun setupWithSize(_ size: CGSize)

Example 2

fun setup(with size: CGSize)

Example 3

fun setup(withParentSize size: CGSize)

Reference

https://github.com/hyperoslo/Spots/pull/468#pullrequestreview-20878625

Move Item back into Spots repository

To eliminate how dependent we are on meta when using Item. It might be a good idea to move it back to Spots and add more mappable structs. The same approach that we are doing for Component when it comes to Interaction and Layout. That make it a lot easier to test and I think we can reduce the dependency that we have on meta a lot. I think it should be used more for edge-cases instead of something that we always depend on.

UITextField and NSTextField?

Trying to see how to extend this spots for nstextfield. any idea?

This where I think it will be implemented, feel free to let me know if it is not the correct way
https://github.com/hyperoslo/Spots/blob/master/Sources/Shared/Library/TypeAlias.swift

#if os(OSX)
  public typealias View = NSView
  public typealias ScrollView = NoScrollView
  public typealias TableView = NSTableView
  public typealias CollectionView = NSCollectionView
  public typealias TextField = NSTextField
#else
  public typealias View = UIView
  public typealias ScrollView = UIScrollView
  public typealias TableView = UITableView
  public typealias CollectionView = UICollectionView
  public typealias TextField = UITextfield
#endif

Since I want it to be list of text fields. I will need to modify Listable to include textField
https://github.com/hyperoslo/Spots/blob/master/Sources/Shared/Library/Listable.swift

/// Gridable is protocol for Spots that are based on UICollectionView
public protocol Listable: Spotable {

   var tableView: TableView { get }
   var textField: TextField { get}
}

then... it become interesting
https://github.com/hyperoslo/Spots/blob/master/Sources/Mac/Spots/ListSpot.swift
ListSpot need to conform to protocol Listable. since textField added in Listable protocol. where will be the right place to add textField in ListSpot?


Improve the use of Paginate enum

When @JohnSundell's new CarouselSpot implementation is merge, it pretty much renders the current implementation of Paginate useless as we now share the same behaviour for both .page and .item based scrolling. However, in that implementation we assume that the target view should always be centered on screen. We could consider keeping Paginate but change it's values to be:

enum Paginate: String {
  case left, center, right, disabled
}

Depending on what paginate option you decide to go with, the snap-to feature would then adjust the targetContentOffset.pointee based on the desired alignment.

I don't see this as something crucial to add now as .center covers what we need at the moment and for me personally, it would be my default behaviour.

Just thought I'd write it down before the idea ran away and evaporated 😎

Refactor user defined views to regular platform views

It would be super groovy if you no longer had to pick a core UI element to inherit from building view.
What this would mean is, if you are developing for iOS, all your project views would inherit from UIView instead of UITableViewCell or UICollectionView.

We would have to refactor Registry to available globally instead of a property on the spotable object. But I think that would just help when setting up the configuration for Spots. This would also mean that the views would be globally available and indifferent to the underlaying UI element used for rendering. One big advantage is that one view could be used everywhere, so only one view per identifier instead of multiple views sharing the same identifier but in different domains.

For me, this feels like the next logical step forward when it comes to Spots, and I'm super eager to get cracking on this issue.

Improve the documentation

The library is not documented well enough, I've started documenting all the methods and properties in this PR #289 but I think we can do much better than that. This will be an ongoing task and it is important to remember to always add docs when adding new classes, structs, methods or properties etc.

All in one App ?

I began to realize that Spots can be used to create all-in-one app. For example consider, one app which does everything from cabs, food ordering, news reading, movies ticket booking etc etc and these apps can be installed as plugins in main app which in turn can be deleted too if user wanted.

Yes, the idea is similar to WeChat. Now my question is do we have this thing in mind or are we moving in that direction? I think we have a solid foundation to start.

Clarify SpotConfigurable

Hi, for now we have View adopting SpotConfigurable, which confuses, at least for me at first. Because SpotConfigurable involves something that can be configurable on the Spotable object, instead of the View

public protocol SpotConfigurable: ItemConfigurable {

  /// The perferred view size of the view.
  var preferredViewSize: CGSize { get }

  func prepareForReuse()
}

Expose scrollViewDidScroll?

Sometimes we need to detect scrolling in the SpotsController subclass to do something, is it good to expose scrollViewDidScroll via delegate?

Improve safety when `kind` cannot be resolved into a class

I've seen some crashes when a ViewModel.kind cannot be resolved in the adapters. This causes Spots to crash because it tries to dequeue something that hasn't been registered.

We need to improve the adapters to always default to the Spotable default view if the kind is not present inside the ViewRegistry.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.