Giter Club home page Giter Club logo

natalie's Introduction

Natalie

Natalie - Storyboard Code Generator (for Swift)

Swift

Current codebase is Swift 4 compatible.

Swift 3.x code may be found from swift3 branch

Swift 2.x code may be found from swift2 branch

Swift 1.x code may be found from swift2 branch

Synopsis

Natalie generates Swift code based on storyboard files to make work with Storyboards and segues easier. Generated file reduce usage of Strings as identifiers for Segues or Storyboards.

Proof of concept implementation to address the String issue for strongly typed Swift language. Natalie is a Swift command-line application (written in Swift) that produces a single .swift file with a bunch of extensions to project classes along the generated Storyboard enum.

Natalie is written in Swift and requires Swift to run. The project uses SWXMLHash as a dependency to parse XML and due to framework limitations.

Enumerate Storyboards

Generated enum Storyboards with a convenient interface (drop-in replacement for UIStoryboard).

struct Storyboards {
    struct Main {...}
    struct Second {...}
    ...

Instantiate initial view controller for storyboard

let vc = Storyboards.Main.instantiateInitialViewController()

Instantiate ScreenTwoViewController in storyboard, using storyboard id

let vc = Storyboards.Main.instantiateScreenTwoViewController()

example usage for prepareForSegue()

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
  if segue == MainViewController.Segue.ScreenOneSegue {    
    let viewController = segue.destinationViewController as? MyViewController
    viewController?.view.backgroundColor = UIColor.yellowColor()
  }
}

...it could be switch { } statement, but it's broken.

Segues

Perform segue

self.perform(segue: MainViewController.Segue.ScreenOneSegue, sender: nil)

Each custom view controller is extended with this code and provide a list of available segues and additional information from Storyboard.

Segue enumeration contains list of available segues

kind property represent types Segue

destination property return type of destination view controller.

extension MainViewController {

    enum Segue: String, Printable, SegueProtocol {
        case ScreenOneSegueButton = "Screen One Segue Button"
        case ScreenOneSegue = "ScreenOneSegue"

        var kind: SegueKind? {
            ...
        }

        var destination: UIViewController.Type? {
            switch (self) {
            case ScreenOneSegueButton:
                return ScreenOneViewController.self
            case ScreenOneSegue:
                return ScreenOneViewController.self
            default:
                assertionFailure("Unknown destination")
                return nil
            }
        }

        var identifier: String { return self.description }
        var description: String { return self.rawValue }
    }
}

Reusable Views To Improve Performance

Collections and tables views use reuseidentifier on cell to recycle a view.

If you define it, their custom view controllers will be extended with a Reusable enumeration, which contains list of available reusable identifiers

example to dequeue a view with Reusable enumeration with UITableView:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(ScreenTwoViewController.Reusable.MyCell, forIndexPath: indexPath) as! UITableViewCell
    cell.textLabel?.text = "\(indexPath.row)"
    return cell
}

Before dequeuing your view, you must register a class or a xib for each identifier. If your cell view has custom class defined in storyboard, in your controller you can call directly

override func viewDidLoad()  {
    tableView.registerReusableCell(MainViewController.Reusable.MyCell)
}

You can pass the view instead - the view must define the reuseidentifier

    tableView.registerReusableCell(tableViewCell)

If your reusable custom view, you can also execute code according to reusable values

class MyCustomTableViewCell: UITableViewCell {
    override func prepareForReuse() {
        if self == MyCustomTableViewController.Reusable.MyCell {
            ...
        }
        else if self == MyCustomTableViewController.Reusable.mySecondCellId {
            ...
        }
    }
}

Colors (iOS 11, macOS 10.13)

Generate an UIColor (or NSColor) static property for each asset colors used in your storyboard.

Installation

Swift Package Manager

$ git clone https://github.com/krzyzanowskim/Natalie.git
$ cd Natalie
$ ./scripts/build.sh
$ Binary at path ./Natalie/natalie

if you want easy Xcode integration you may want to install the binary to be easily accessible for any application from /usr/local/bin

$ cp natalie /usr/local/bin

Homebrew

$ brew install natalie

You can also put natalie executable file at the root of your project folder and keep it under version control. This way everyone even your CI will be able to generate the files.

Xcode Integration

Natalie can be integrated with Xcode in such a way that the Storyboards.swift the file will be updated with every build of the project, so you don't have to do it manually every time.

This is my setup created with New Run Script Phase on Build Phase Xcode target setting. It is important to move this phase above Compilation phase because this file is expected to be up to date for the rest of the application.

  • Select the project in the Project Navigator on the left of your Xcode window
  • Select your App Target in the list
  • Go in the "Build Phases" tab
  • Click on the "+" button on the upper left corner and choose "New Run Script Phase" and copy/paste script:
# Adjust path to "natalie" binary
# NATALIE_PATH="$PROJECT_DIR/natalie"
NATALIE_PATH="/usr/local/bin/natalie"

if [ -f $NATALIE_PATH ]; then
    echo "Natalie Generator: Determining if generated Swift file is up-to-date."
    
    BASE_PATH="$PROJECT_DIR/$PROJECT_NAME"
    OUTPUT_PATH="$BASE_PATH/Storyboards.swift"

    if [ ! -e "$OUTPUT_PATH" ] || [ -n "$(find "$BASE_PATH" -type f -name "*.storyboard" -newer "$OUTPUT_PATH" -print -quit)" ]; then
        echo "Natalie Generator: Generated Swift is out-of-date; re-generating..."

        /usr/bin/chflags nouchg "$OUTPUT_PATH"
        "$NATALIE_PATH" "$BASE_PATH" > "$OUTPUT_PATH"
        /usr/bin/chflags uchg "$OUTPUT_PATH"

        echo "Natalie Generator: Done."
    else
        echo "Natalie Generator: Generated Swift is up-to-date; skipping re-generation."
    fi
else 
    echo "error: Could not find Natalie Generator at $NATALIE_PATH; Please visit https://github.com/krzyzanowskim/Natalie for installation instructions."
    exit 1
fi
  • add Storyboards.swift to the project.

Usage:

Download Natalie from Github: https://github.com/krzyzanowskim/Natalie and use it in the console, for example like this:

$ git clone https://github.com/krzyzanowskim/Natalie.git
$ cd Natalie

The command expects one of two types of parameters:

  • path to a single .storyboard file
  • path to a folder

If the parameter is a Storyboard file, then this file will be used. If a path to a folder is provided Natalie will generate code for every storyboard found inside.

$ natalie NatalieExample/NatalieExample/Base.lproj/Main.storyboard > NatalieExample/NatalieExample/Storyboards.swift

Contribution

Please submit Pull Request against current development branch.

Author and contact

Marcin Krzyżanowski

Licence

The MIT License (MIT)

Copyright (c) 2015 Marcin Krzyzanowski

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

natalie's People

Contributors

a2 avatar alkalim avatar barrault01 avatar binkdotli avatar brandons avatar callumoz avatar chrisfsampaio avatar defrenz avatar e-marchand avatar ezefranca avatar fantattitude avatar frizlab avatar jai avatar kostiakoval avatar krzyzanowskim avatar marcelofabri avatar mathiasnagler avatar nhojb avatar phimage avatar pmairoldi avatar screenname avatar shulepov avatar tonyarnold avatar yoiang avatar

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

natalie's Issues

Have Natalie be smart about targets in xcodeproj

One feature request I'd like is for Natalie to allow an xcodeproj file as argument, parse it to find out which storyboards are included in which targets and generate a Storyboards.swift for each target. Like that, if the project has storyboards in an app target and in an extension target, Natalie will generate a separate file to include in each target that only makes the targets storyboards visible. It will also solve name collisions when targets have storyboards with the same name.

As a side note, I'm currently implementing this by wrapping Natalie around a ruby script which parses an xcodeproj file with the Xcodeproj gem (which came out of the Cocoapods project) and feeds Storyboard files to natalie. Unfortunately, there doesn't seem to be equally good Xcodeproj parsing libs in Swift.

Build failure on macOS 10.13 with Xcode 9 (swift 3.2)

Cannot build 0.5.0 on macOS 10.13 with Xcode 9:

$ swift build -c release -Xswiftc -static-stdlib
Compile Swift Module 'natalie' (14 sources)
natalie.XMLIndexer:72:22: error: invalid redeclaration of 'Element'
    public typealias Element = natalie.XMLIndexer
                     ^
/private/tmp/Natalie-0.5.0/Sources/natalie/SWXMLHash/SWXMLHash.swift:359:10: note: 'Element' previously declared here
    case Element(XMLElement)
         ^
error: terminated(1): /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift-build-tool -f /private/tmp/Natalie-0.5.0/.build/release.yaml main

Some code is generated even when it's not used

UICollectionViewController and UITableViewController extension is generated even when they are not used in the application.
The code should be generated only when they are used. It would make code cleaner.

Steps :

  • Remove all from the storyboard
  • Save and build, generate it.

The files contains 145 lines of code.

I see 2 improvements:

  • Don't generated code that not used
  • Move some base functionality to separate file, if it's possible.

instantiateInitialController return type should be scoped by enclosing module

This is an issue if the storyboard has the same name as the initial controller e.g.

struct Storyboards {
    struct FooViewController: Storyboard {
        static func instantiateInitialController() -> FooViewController {
            return self.storyboard.instantiateInitialController() as! FooViewController
        }
    }
}

Opps! instantiateInitialController returns a Storyboards.FooViewController, not a Module.FooViewController :-(

The obvious work-around is not to name storyboards using the same name as the initial view controller. However for simple storyboards this can be a convenient and natural naming.

Looks like natalie just needs to parse the "customModule" attribute for "viewControllers" in the storyboard xml.

./scripts/build.sh - doesn't seem to work

Hello,

The build.sh script doesn't seem to build anything.

BUILD_PATH=`xcrun swift build -c release --static-swift-stdlib --show-bin-path`
cp "$BUILD_PATH/natalie" .
echo "Binary at path $PWD/natalie"

It looks like SPM doesn't build anything and just prints bin build path if --show-bin-path option is given

It builds fine if you just run swift build -c release --static-swift-stdlib

Can I submit PR for this?

Question about ViewControllers and XIB Files

Hi, Great project!

One question this lib works for Strings like nibNames? for example Im trying to don't use SB and create my controllers via XIB files so I generally I will have something like this in the ViewController:

init(viewModel: ViewModel) {
        self.viewModel = viewModel
        super.init(nibName: "LoginView", bundle: nil)
    }

Just like with Segues or Cell identifiers would be nice to be able to use this lib to avoid the burned "LoginView" XIB file name, is this possible?

Ambiguous use of 'selection'

If there is more that 1 view controller with segue, the Swift can't find which to use
Steps:

  • add one more viewController with segue int example project.

Result:

if let selection = segue.selection() { 

This code won't work.
To make it work I need to manually specify type.

 if let selection: MainViewController.Segue = segue.selection()  {

Refactoring with Natalie in a project.

We have a project that came with Natalie installed and contains a very large storyboard. Our next step is that we want to refactor this main storyboard into a number of smaller storyboards. Do you have any suggestions of how to do this with Natalie installed? At the moment, as I understand it, there are a few issues being caused?

I can't really find any documentation on this and the team members (@gslondon) haven't really used Natalie before? If you could give some suggestions, documentations or blog posts about this that would be really helpful!

Segues tied to gesture recognizers (non-manual segues) aren't processed

So if a segue appears outside the viewController element in the objects element, then it doesn't get indexed/processed, as in here - the ShopImageSegue won't appear in the generated swift file. I don't have a solution yet but wanted to post it here.

    <scene sceneID="04j-HW-gGp">
        <objects>
            <tableViewController storyboardIdentifier="RBShopDetailViewController" id="NHg-Ry-QDF" customClass="RBShopDetailViewController" customModule="Ramen_Beast" customModuleProvider="target" sceneMemberID="viewController">
                <connections>
                //
                    //MANUAL SEGUES APPEAR HERE
                    //

                </connections>
            </tableViewController>
            <placeholder placeholderIdentifier="IBFirstResponder" id="Hjv-Px-2Va" userLabel="First Responder" sceneMemberID="firstResponder"/>
            <tapGestureRecognizer id="OXk-v3-cJn" userLabel="Left Image Tap Recognizer">
                <connections>
                    <outlet property="delegate" destination="NHg-Ry-QDF" id="Kbi-ot-jDB"/>
                    <segue destination="Yqq-hT-2nd" kind="show" identifier="ShopImageSegue" id="kFM-z9-bmD"/>
                </connections>
            </tapGestureRecognizer>
            <tapGestureRecognizer id="fHf-lD-POd" userLabel="Right Image Tap Recognizer"/>
        </objects>
        <point key="canvasLocation" x="2288" y="-349"/>
    </scene>

OS X support is broken

Running what's in master against my OS X Storyboard, I get references to UIStoryboard and UIViewController. Changing these to NSStoryboard and NSViewController gives me an error: ambiguous use of identifier (on line 13).

//
// Autogenerated by Natalie - Storyboard Generator Script.
// http://blog.krzyzanowskim.com
//

import Cocoa

//MARK: - Storyboards

extension UIStoryboard {
    func instantiateViewController<T: UIViewController where T: IdentifiableProtocol>(type: T.Type) -> T? {
        let instance = type.init()
        if let identifier = instance.identifier {
            return self.instantiateViewControllerWithIdentifier(identifier) as? T
        }
        return nil
    }
}

protocol Storyboard {
    static var storyboard: NSStoryboard { get }
    static var identifier: String { get }
}

struct Storyboards {

    struct Main: Storyboard {

        static let identifier = "Main"

        static var storyboard: NSStoryboard {
            return NSStoryboard(name: self.identifier, bundle: nil)
        }

        static func instantiateWindowControllerWithIdentifier(identifier: String) -> NSWindowController {
            return self.storyboard.instantiateControllerWithIdentifier(identifier) as! NSWindowController
        }

        static func instantiateViewController<T: UIViewController where T: IdentifiableProtocol>(type: T.Type) -> T? {
            return self.storyboard.instantiateViewController(type)
        }

        static func instantiateViewControllerWithIdentifier(identifier: String) -> NSViewController {
            return self.storyboard.instantiateControllerWithIdentifier(identifier) as! NSViewController
        }

        static func instantiateViewController<T: UIViewController where T: IdentifiableProtocol>(type: T.Type) -> T? {
            return self.storyboard.instantiateViewController(type)
        }

        static func instantiateDocumentWindowController() -> NSWindowController {
            return self.storyboard.instantiateControllerWithIdentifier("Document Window Controller") as! NSWindowController
        }
    }
}

//MARK: - ReusableKind
enum ReusableKind: String, CustomStringConvertible {
    case TableViewCell = "tableViewCell"
    case CollectionViewCell = "collectionViewCell"

    var description: String { return self.rawValue }
}

//MARK: - SegueKind
enum SegueKind: String, CustomStringConvertible {    
    case Relationship = "relationship" 
    case Show = "show"                 
    case Presentation = "presentation" 
    case Embed = "embed"               
    case Unwind = "unwind"             
    case Push = "push"                 
    case Modal = "modal"               
    case Popover = "popover"           
    case Replace = "replace"           
    case Custom = "custom"             

    var description: String { return self.rawValue } 
}

//MARK: - SegueProtocol
public protocol IdentifiableProtocol: Equatable {
    var identifier: String? { get }
}

public protocol SegueProtocol: IdentifiableProtocol {
}

public func ==<T: SegueProtocol, U: SegueProtocol>(lhs: T, rhs: U) -> Bool {
    return lhs.identifier == rhs.identifier
}

public func ~=<T: SegueProtocol, U: SegueProtocol>(lhs: T, rhs: U) -> Bool {
    return lhs.identifier == rhs.identifier
}

public func ==<T: SegueProtocol>(lhs: T, rhs: String) -> Bool {
    return lhs.identifier == rhs
}

public func ~=<T: SegueProtocol>(lhs: T, rhs: String) -> Bool {
    return lhs.identifier == rhs
}

public func ==<T: SegueProtocol>(lhs: String, rhs: T) -> Bool {
    return lhs == rhs.identifier
}

public func ~=<T: SegueProtocol>(lhs: String, rhs: T) -> Bool {
    return lhs == rhs.identifier
}

//MARK: - ReusableViewProtocol
public protocol ReusableViewProtocol: IdentifiableProtocol {
    var viewType: NSView.Type? { get }
}

public func ==<T: ReusableViewProtocol, U: ReusableViewProtocol>(lhs: T, rhs: U) -> Bool {
    return lhs.identifier == rhs.identifier
}

//MARK: - Protocol Implementation
extension NSStoryboardSegue: SegueProtocol {
}

//MARK: - NSViewController extension
extension NSViewController {
    func performSegue<T: SegueProtocol>(segue: T, sender: AnyObject?) {
        if let identifier = segue.identifier {
            performSegueWithIdentifier(identifier, sender: sender)
        }
    }

    func performSegue<T: SegueProtocol>(segue: T) {
        performSegue(segue, sender: nil)
    }
}

//MARK: - NSWindowController extension
extension NSWindowController {
    func performSegue<T: SegueProtocol>(segue: T, sender: AnyObject?) {
        if let identifier = segue.identifier {
            performSegueWithIdentifier(identifier, sender: sender)
        }
    }

    func performSegue<T: SegueProtocol>(segue: T) {
        performSegue(segue, sender: nil)
    }
}


//MARK: - ViewController

Natalie breaks when using IBAnimatable

I'm not even sure if a fix is possible since it's caused by a third party dependency. But I get build errors when Natalie generates code for a view controller that is subclassed from AnimatableViewController of IBAnimatable

Change approach

Hi,
I want to suggest to slightly change the approach of what should be generated.
Currently generated extensions for view controllers that overrides some property.

But it has one drawback:

  • only one view controller in inheritance hierarchy can have storyboard identifier, or generated file won't compile (as extensions can override properties and methods only once, e.g. if we have two view controllers BaseViewController and ExtendedViewController: BaseViewController, that both have storyboard identifiers);

So, instead of generating extensions for view controllers can be generated entities for storyboard identifiers, e.g.:

protocol StoryboardIdentifier {
    var name: String { get }
}

protocol ViewControllerIdentifier: StoryboardIdentifier {
    func instantiateViewControllerFromStoryboard(storyboard: Storyboards) -> UIViewController! 
}

And for each identifier in storyboard generate class:

final class MenuViewControllerIdentifier: ViewControllerIdentifier {
    var name: String { return "MenuViewController" }
    func instantiateViewControllerFromStoryboard(storyboard: Storyboards) -> MenuViewController! {
         return storyboard.instantiateViewControllerWithIdentifier(self.name) as? MenuViewController
    }
}

Such approach implies to better name identifiers in storyboards.

Or second solution, something like this (similar whats done for segues):

extension MenuViewController {
     class StoryboardEntry {
            case MainMenu("MainMenu")
            case SecondaryMenu("SecondaryMenu")
            func instantiateFromStoryboard(storyboard: Storyboards) -> MenuViewController! {
                   return storyboard.instantiateViewControllerWithIdentifier(self.rawValue) as? MenuViewController
            }
     }
}

P.S. Possibly better names can be given to the protocols and generated classes.

New issue after XCode 7 installarion

I'm having a new issue after xcode 7 kicked in. I have 2 xcode installations now, one with 6.4 and one with 7. The only thing that doesn't pass during the build phase is Natalie. It throws me "Command /bin/sh failed with exit code 133"

and "Natalie generator
dyld: Library not loaded: @rpath/libswiftSecurity.dylib
Referenced from: /usr/local/bin/natalie.swift
Reason: image not found"

and "/usr/local/bin/natalie.swift "$PROJECT_DIR/$PROJECT_NAME" > "$PROJECT_DIR/$PROJECT_NAME/Layout/Storyboards.swift""

Anyone has the same issue? Did I mess up when doubling the installation?

Ambiguous type name 'Segue'

Hello,

I have just integrated Natalie with my project using the build script given in the README.
When trying to compile the generated Storyboards.swift, I am having the error Ambiguous type name 'Segue'.

The issue is caused on a custom view controller inheriting from another custom view controller, therefore, on the child view controller, the type Segue can refer to neither it's own type or it's parent's type.

The generated code looks like the following:

extension X { 

    enum Segue: String, CustomStringConvertible, SegueProtocol {
        case viewDetail = "viewDetail"

        var kind: SegueKind? {
            switch (self) {
            case .viewDetail:
                return SegueKind(rawValue: "show")
            }
        }

        var destination: UIViewController.Type? {
            switch (self) {
            default:
                assertionFailure("Unknown destination")
                return nil
            }
        }

        var identifier: String? { return self.description } 
        var description: String { return self.rawValue }
    }

}

extension UIStoryboardSegue {
    func selection() -> Y.Segue? {  // <-- Error on this line
        if let identifier = self.identifier {
            return DeliveryOrderHistoryViewController.Segue(rawValue: identifier)
        }
        return nil
    }
}

extension Y { 

    enum Segue: String, CustomStringConvertible, SegueProtocol {
        case viewDetail = "viewDetail"

        var kind: SegueKind? {
            switch (self) {
            case .viewDetail:
                return SegueKind(rawValue: "show")
            }
        }

        var destination: UIViewController.Type? {
            switch (self) {
            default:
                assertionFailure("Unknown destination")
                return nil
            }
        }

        var identifier: String? { return self.description } 
        var description: String { return self.rawValue }
    }

}

On the code above, Y inherits from X, which in turn inherits from UIViewController.

Do you have any recommendations on how to fix the issue?

Generate ids for localizable strings and images

I'm not sure that this functionality should be placed directly inside Natalie, may be should be created a different tool.

Generating identifiers for storyboards stuff definitely is a good step :) But there are also quite a lot of magic strings in the project - for localizable strings (NSLocalizedString("Hello", ...)) or for images UIImage(named: "Logo"), and other files in the bundle.

So, may be also can be generated analogue of R file on android. So, for strings and images can be generated something like:

struct Strings {
     static let Hello = "Hello"
}
struct Images {
    static let Logo = "Logo"
}

And for convenience:

struct LocalizedString {
     static var Hello = { return NSLocalizedString(Strings.hello, ...) }
}

Swift 2.0

Any plans for converting Natalie to Swift 2.0?

Location of natalie.swift

Is there a particular reason that natalie.swift is placed outside PROJECT_DIR and the example project actually uses the natalie.swift located at /usr/local/bin as opposed to the included natalie.swift? If it's not on purpose let me know and I'll refactor

natalie binary of 0.6.5 does not work after updating to Xcode 9.3

Executing natalie, even with no arguments, in commandline throws an error like this:
dyld: Symbol not found: __T0s11CommandLineO9argumentsSaySSGvZ.

swift --version outputs the following after switching command-line-tools back to 9.2 (9C40b):
Apple Swift version 4.0.3 (swiftlang-900.0.74.1 clang-900.0.39.2)
Target: x86_64-apple-macosx10.9

If I build the natalie project in Xcode 9.2 or 9.3 and execute the generated executable it just works fine.

Maybe the natalie executable is not compatible with the updated swift version in the system?

Regenerate Storyboards.swift only if needed

The proposed run script build phase in the Readme will re-generate Storyboards.swift at each build.
The generation in itself is long. Add to this that Xcode have to re-compile the generated file each time, it took me three builds to get annoyed…

Here’s a proposed script that detects whether re-generating the file is actually needed before re-generating it.

echo "Natalie Generator: Determining if generated Swift file is up-to-date."

NATALIE_PATH="$PROJECT_DIR/Dependencies/natalie/natalie.swift"

GO=0
BASE_PATH="$PROJECT_DIR/$PROJECT_NAME"
OUTPUT_PATH="$BASE_PATH/Storyboards.swift"
if [ ! -e "$OUTPUT_PATH" ]; then
    GO=1
elif [ -n "$(find "$BASE_PATH" -type f -name "*.storyboard" -newer "$OUTPUT_PATH" -print -quit)" ]; then
    GO=1
fi

if [ $GO -eq 1 ]; then
    echo "Natalie Generator: Generated Swift is out-of-date; re-generating..."
    "$NATALIE_PATH" "$PROJECT_DIR/$PROJECT_NAME" >"$OUTPUT_PATH"
    echo "Natalie Generator: Done."
else
    echo "Natalie Generator: Generated Swift is up-to-date; skipping re-generation."
fi

unexpectedly found nil while unwrapping an Optional value

Whenever I tried to generate storyboard code that has storyboardID for a storyboard reference, the Natalie execution stops and raises an error _"unexpectedly found nil while unwrapping an Optional value
Illegal instruction: 4" _
- See image attached for more understanding

sample

Swift 4 issue in console

Hi, @krzyzanowskim) thanks for the lib, after migrating on Swift 4, I have this issue:

*** ......../Storyboards/Storyboards.swift:535:5: implicit Objective-C entrypoint -[VIEW CONTROLLER CLASS NAME storyboardIdentifier] is deprecated and will be removed in Swift 4; add explicit '@objc' to the declaration to emit the Objective-C entrypoint in Swift 4 and suppress this message

Do you know how to fix it?

Xcode Integration problem

First of all.
Thank you, this tool is a great idea.

Unfortunately, I am having issues with the Xcode Integration, I am using Xcode 6.3.

Everything goes well when running natalie.swift from the command line.
I have copied natalie.swift to /usr/local/bin.

I have added the script to the build phases like so:
Build phases screen capture

When Xcode does it's automatic building it seems to work, the Storyboards.swift file is updated.
However, when I build or run the app, I get many errors:
Errors

I hope I am just missing something.

Callum

Issue with inheritance

Hi,

I recently faced an issue with Natalie. Given the following architecture:

class A: UIViewController {}
class B: A {}

with both A and B identifiable view controllers in my Storyboard.

Natalie generates this code that doesn't compile:


//MARK: - AViewController
extension AViewController: IdentifiableProtocol { 
    var storyboardIdentifier: String? { return "AViewController" }
    static var storyboardIdentifier: String? { return "AViewController" }
}


//MARK: - BViewController
extension BViewController: IdentifiableProtocol { 
    var storyboardIdentifier: String? { return "BViewController" }
    static var storyboardIdentifier: String? { return "BViewController" }
}

and generates the following errors:

./Storyboards.swift:248:9: error: overriding declaration requires an 'override' keyword
    var storyboardIdentifier: String? { return "BViewController" }
        ^
./Storyboards.swift:241:9: note: overridden declaration is here
    var storyboardIdentifier: String? { return "AViewController" }
        ^
./Storyboards.swift:249:16: error: overriding declaration requires an 'override' keyword
    static var storyboardIdentifier: String? { return "BViewController" }
               ^
./Storyboards.swift:242:16: note: overridden declaration is here
    static var storyboardIdentifier: String? { return "AViewController" }
               ^
./Storyboards.swift:249:16: error: class var overrides a 'final' class var
    static var storyboardIdentifier: String? { return "BViewController" }
               ^
./Storyboards.swift:242:16: note: overridden declaration is here
    static var storyboardIdentifier: String? { return "AViewController" }
               ^
./Storyboards.swift:247:28: error: redundant conformance of 'BViewController' to protocol 'IdentifiableProtocol'
extension BViewController: IdentifiableProtocol { 
                           ^
./BViewController.swift:11:7: note: 'BViewController' inherits conformance to protocol 'IdentifiableProtocol' from superclass here
class BViewController: AViewController {
      ^

Here is a sample project reproducing the issue: https://github.com/delannoyk/NatalieInheritanceIssue

identifiers without custom view controllers are not generated

some view controllers don't really need a custom class (at least in my case), so with your awesome script (which helped a lot), I can't get to those VCs without knowing their identifier by code.. wouldn't it be more helpful if you generate the "identifiers" of those VCs into an enum or smthn?

Rename Parser class?

I am just wondering if Parser could be renamed to something else, for instance : Template or Renderer

because this class essentially output data, and do not parse it , even if I made a lot of lazy var (so the parsing could occur at that moment)

using a switch statement in prepareForSegue

Im interested to know what is actually broken here, what is it that you are trying to achieve?

Here is a playground I was messing about in, could you please give me one in a broken state?

enum Segue: String {
  case ScreenOneSegue = "abcdef"
}

func prepareForSegue(storyboardSegue: UIStoryboardSegue, sender: AnyObject?) {

//  if storyboardSegue == Segue.ScreenOneSegue {
//    let viewController = storyboardSegue.destinationViewController
//    viewController?.view.backgroundColor = UIColor.yellowColor()
//  }

  guard let identifier = storyboardSegue.identifier, segue = Segue(rawValue: identifier) else {
    return
  }

  switch segue {

  case .ScreenOneSegue:
    print("got here")

  }

  switch storyboardSegue {

  case _ where storyboardSegue == Segue.ScreenOneSegue:
    print("got here")

  default:
    break

  }

}

func ==<T: RawRepresentable where T.RawValue == String>(x: UIStoryboardSegue, y: T) -> Bool {
  return x.identifier == y.rawValue
}

From what I can gather you are trying to remove the need for

guard let identifier = storyboardSegue.identifier, segue = Segue(rawValue: identifier) else {
  return
}

and perform a switch on the UIStoryboardSegue itself

performSegue should accept sender of type Any?

func perform<T: SegueProtocol>(segue: T, sender: AnyObject?) { if let identifier = segue.identifier { performSegue(withIdentifier: identifier, sender: sender) } }

The above method accepts AnyObject as the sender parameter, but the framework performSegue method has Any as the sender type.

Always up to date

the script always says I'm up to date even if there are a changes

File generation refactor

We need to be able to analyse the generated file before printing it (see #15). Say if we need to remove duplicated imports, or move all the imports to the file head, that cannot be done with the current setup.
My suggestion is to hold the generated file on a string and do the post analysis before outputing it.

Does that make sense? Any thoughts?

Custom UINavigationController class in multiple storyboards causes build errors

If I have two storyboards, both containing a UINavigationController which inherits from a custom class (eg. CustomNavigationController), the resulting Storyboards.swift file does not compile. It fails with "'CustomNavigationController IdentifiableProtocol' is ambiguous for type lookup in this context" errors.

See attached sample project.

Is there a possible fix or workaround?

NatalieBug.zip

Genre only ids

I want to get only ids without any extra functionality. It would be awesome if there would be some option for that.

Here is what I want

struct Storyboards {
  struct Main {
    enum ids: String {
      case Login
      case Done
      case Location
      case Contacts
      case Hello
    }
  }
}

Bug with multiple Storyboards

I have a project with multiple Storyboard files, and for some reason, when I run the script on my project folder I get this:

//MARK: - Storyboards
enum Storyboards: String {
    case Filter = "Filter"
    case Main = "Main"
    case Results = "Results"

    private var instance:UIStoryboard {
        return UIStoryboard(name: self.rawValue, bundle: nil)
    }

    func instantiateInitialViewController() -> UIViewController? {
        switch (self) {
        case Filter:
            return self.instance.instantiateInitialViewController() as! NavigationController
        default:
            return self.instance.instantiateInitialViewController() as? UIViewController
        }
        switch (self) {
        case Main:
            return self.instance.instantiateInitialViewController() as! LoginViewController
        default:
            return self.instance.instantiateInitialViewController() as? UIViewController
        }
        switch (self) {
        case Results:
            return self.instance.instantiateInitialViewController() as! NavigationController
        default:
            return self.instance.instantiateInitialViewController() as? UIViewController
        }
    }

    func instantiateViewControllerWithIdentifier(identifier: String) -> UIViewController {
        return self.instance.instantiateViewControllerWithIdentifier(identifier) as! UIViewController
    }
}

As you can see, for some reason, it is creating one switch for each Storyboard instead of just one switch.
Because of this, only the first switch is executed.

While I have your attention, it would be cool, if it was possible to instantiate an initial VC and immediately get the correct VC type, at the moment, this method only returns a UIViewController?

I am using the latest version of the script.

Callum

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.