{"id":3203,"url":"https://github.com/krzysztofzablocki/Playgrounds","last_synced_at":"2025-08-03T13:32:08.852Z","repository":{"id":22253518,"uuid":"25587159","full_name":"krzysztofzablocki/Playgrounds","owner":"krzysztofzablocki","description":"Better playgrounds that work both for Objective-C and Swift","archived":false,"fork":false,"pushed_at":"2022-07-19T10:36:17.000Z","size":15126,"stargazers_count":2635,"open_issues_count":1,"forks_count":163,"subscribers_count":106,"default_branch":"master","last_synced_at":"2024-12-02T16:27:54.686Z","etag":null,"topics":["hot-reload","hot-reloading","objective-c","playground","real-time","swift","swift-playgrounds"],"latest_commit_sha":null,"homepage":"http://twitter.com/merowing_","language":"Objective-C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/krzysztofzablocki.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":"Funding.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"custom":"https://www.merowing.info/membership","github":"krzysztofzablocki"}},"created_at":"2014-10-22T14:52:40.000Z","updated_at":"2024-11-23T09:42:46.000Z","dependencies_parsed_at":"2022-07-17T13:00:30.488Z","dependency_job_id":null,"html_url":"https://github.com/krzysztofzablocki/Playgrounds","commit_stats":null,"previous_names":["krzysztofzablocki/kzplayground"],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/krzysztofzablocki%2FPlaygrounds","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/krzysztofzablocki%2FPlaygrounds/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/krzysztofzablocki%2FPlaygrounds/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/krzysztofzablocki%2FPlaygrounds/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/krzysztofzablocki","download_url":"https://codeload.github.com/krzysztofzablocki/Playgrounds/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228468903,"owners_count":17924949,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["hot-reload","hot-reloading","objective-c","playground","real-time","swift","swift-playgrounds"],"created_at":"2024-01-05T20:16:34.360Z","updated_at":"2024-12-07T01:30:40.552Z","avatar_url":"https://github.com/krzysztofzablocki.png","language":"Objective-C","funding_links":["https://www.merowing.info/membership","https://github.com/sponsors/krzysztofzablocki"],"categories":["Rapid Development","WebSocket","Objective-C","HarmonyOS"],"sub_categories":["Prototyping","Other free courses","Web View","Windows Manager"],"readme":"# Swift Playgrounds... but supporting both Objective-C and Swift code, plus some superb features.\n\n[Watch demo](https://vimeo.com/109757619)\n\n[More in-depth overview video](https://vimeo.com/110175870)\n\n[![](/Screenshots/small_playground.gif?raw=true)](https://vimeo.com/109757619)\n\n[![Version](https://img.shields.io/cocoapods/v/KZPlayground.svg?style=flat)](http://cocoadocs.org/docsets/KZPlayground)\n[![License](https://img.shields.io/cocoapods/l/KZPlayground.svg?style=flat)](http://cocoadocs.org/docsets/KZPlayground)\n[![Platform](https://img.shields.io/cocoapods/p/KZPlayground.svg?style=flat)](http://cocoadocs.org/docsets/KZPlayground)\n\nPlaygrounds are one of the niftiest features of Swift. They allow you to quickly test out bits of code and see results in real time without going through traditional edit-compile-run-debug cycle. \n\n\"But surely playgrounds aren't possible in Objective-C\" you say? ... In fact they can be much better than Swift ones.\n\n# Objective-C Playgrounds\nFeatures:\n- Faster than Swift playgrounds (a lot)\n- Extra controls for tweaking:\n\t- values\n\t- images\n- Auto-animated values\n- Synchronizing DSL's\n- Buttons\n- IDE agnostic, once you run it, you can modify the code even from vim.\n- Full iOS simulator and access to all iOS features, so you can prototype production ready code.\n- Nice DSL for rapid prototyping\n- CocoaPods support, so you can add it to existing projects to experiment\n- Open source, anyone can contribute to make them better!\n\nand it’s just a start.\n\n# Technical details\n![](/Screenshots/playground.png?raw=true)\nFirst, let’s establish naming:\n- Timeline is a place where you have snapshots and controls.\n- Worksheet is a place where you can add views / controls and have interaction with them. You can use all the stuff you’d normally use with iOS like UIGestureRecognizers etc.\n- Tick counter - number of times the code changes have been loaded, multiply by the time it takes to compile + load your project and you see how much time you saved.\n\n## DSL’s - Beautiful and fast way to prototype.\n### Timeline snapshots\n`KZPShow(obj)`\n- CALayer\n- UIView\n- UIBezierPath\n- CGPathRef\n- CGImageRef\n- UIImage\n- NSString, with format or without\n- id\n\n#### Implementing snapshotting for your custom classes\nYou can implement custom debug image:\n\n```objc\n- (UIImage*)kzp_debugImage;\n```\n\nIf you have already implemented `- (id)debugQuickLookObject` that returns any of types supported by the KZPShow, you don’t need to do anything.\n\n### Controls\n- Button\n\n```objc\nKZPAction(@\"Press me\", ^{\n// Magic code\n})\n```\n\n- Images\n\nPicking an image from the library:\n\n```objc\nKZPAdjustImage(myImage);\nKZPWhenChanged(myImage, ^(UIImage *img) {\n  imageView.image = img;\n});\n```\n\n- Values\n\n```objc\nKZPAdjustValue(scale, 0.5f, 1.0f) //- for floats\nKZPAdjustValue(position, 0, 100) //- for integers\n```\n\nyou can also set default values:\n\n```objc\nKZPAdjustValue(position, 0, 100).defaultValue(50)\n```\n\n- Block callbacks `KZPAdjust` are also available.\n\n### Animations\n- Block animation callback, code that will be executed with each screen refresh (display link). Useful for animating multiple values. \n\n```objc\nKZPAnimate(CGFloat from, CGFloat to, void (^block)(CGFloat));\nKZPAnimate(void (^block)());\n```\n\n- Auto-animated values, defines new variable and automatically animates them. AR -\\\u003e AutoReverse\n\n```objc\nKZPAnimateValue(rotation, 0, 360)\nKZPAnimateValueAR(scale, 0, 1)\n```\n\n### Coordinating code execution\n\nExecuting code only once the value is set\n\n```objc\nKZPWhenSet(myImage, ^(UIImage *img) {\n\t//! magic\n});\n```\n\nExecuting code on value changes\n\n```objc\nKZPWhenChanged(myImage, ^(UIImage *img) {\n\t//! magic\n});\n```\n\n### Storing variables\n\n### Transient - Cleared with each code change\n\nInstead of using instance variables / properties for KZPlayground class (you are fine to use them for normal classes that you create as part of playground), you should store playground specific variables that you need to reference between playground methods, eg. view you want to pan with UIPanGestureRecognizer inside transientObjects dictionary.\n\n```objc\nself.transientObjects[@\"pannableView\"] = view;\n```\n\n### Persisted - Not cleared with recompilation\nImplement setup method and use normal instance variables to store data you don't want to change on code change.\neg. if you need to do some expensive operation.\n\nSnapshots recorded during setup will persist in timeline.\n\n```objc\n- (void)setup\n{\n\tself.data = [self fetchBigDataSet];\n}\n```\n\n\n# Installation and setup\nKZPlayground is distributed as a [CocoaPod](http://cocoapods.org):\n`pod 'KZPlayground'`\nso you can either add it to your existing project or clone this repository and play with it. \n\n\u003e Remember to not add playgrounds in production builds (easy with new cocoapods configuration scoping).\n\nOnce you have pod installed, you need to create your playground, it’s simple:\n\n1. Subclass KZPPlayground\n2. Implement run method\n3. Conform to KZPActivePlayground protocol  \nYou can have many playgrounds in one project, but only one should be marked as KZPActivePlayground. It will be automatically loaded.\n4. present `[KZPPlaygroundViewController playgroundViewController]`\n\nTo apply your changes you have 2 approaches:\n\n1. Xcode/Appcode you can use cmd/ctrl + x (done via dyci plugin) while you are modifying your code.\n2. (My Preferrence) Automatic on file save (IDE agnostic) using kicker gem in terminal: (N.B. you need to have the kicker gem installed, see below)  \n\n```bash\nkicker -sql 0.05 FOLDER_WITH_SOURCE_FILES\n```\n\nin case of Example project you'd call kicker from inside the project root folder (one containing the `.kick` file, which you will also need.)\n\n```bash\nkicker -sql 0.05 Example\n```\n\nThis will react to all changes in .m files in the Example directory and reload your playground.\n\n### (Optional) Make Kicker autostart whenever you run your project\n\n1. Copy the .kick file to your project's directory.\n2. Add a new \"Run Script\" under your target's \"Build Phases\" tab with the following:\n\n```\nPID_PATH=/tmp/${PROJECT_NAME}_kicker.pid\nif [ -e $PID_PATH ]\nthen\n  kill $(cat $PID_PATH)\n  rm $PID_PATH\nfi\nkicker -sql 0.05 . \u003e /dev/null 2\u003e\u00261 \u0026 echo $! \u003e $PID_PATH\n```\nNote: You will need to manually kill the kicker process when you're done since it won't be killed after you stop running your project.\n\n### Only once\nKZPlayground is powered by Code Injection, currently using [Dyci](https://github.com/DyCI/dyci-main/) code injection tool, you only need to install it once on your machine (You’ll need to reinstall it on Xcode updates):\n\n```bash\ngit clone https://github.com/DyCI/dyci-main.git\ncd dyci-main/Install/\n./install.sh\n```\n\nIn order to use the [kicker](https://github.com/alloy/kicker) gem, you need to install it as follows:  \n\n```bash\n(sudo) gem install kicker\n```\n\n## Swift support\n\nMy playgrounds now support Swift code, to use that you should use [injectionforxcode](http://injectionforxcode.com) instead of Dyci, make sure to grab 0.4 Release for some simpler API.\n\n## Contributing\n\nPull-requests are welcomed.\n\n## Changelog\n\n### 0.4\n- Basic Swift support \n- Added Swift example\n\n### 0.3.2\n- Ability to hide timeline\n\n### 0.3.1\n- XCAsset images picking.\n- Persisting selected images.\n\n### 0.3.0\n- Image picking.\n- Synchronisations.\n- Change observing.\n- Localizable strings are injected.\n\n### 0.2.5\n- Persistent setup functionality.\n- Improved snapshots details.\n\n### 0.2.0\n- All files in the project can be now changed to trigger playground reload.\n- Better kicker setup.\n- Transient objects.\n\n## License\n\nKZPlayground is available under the modified MIT license. See the LICENSE file for more info.\n\n## Author\n\nKrzysztof Zablocki, krzysztof.zablocki@pixle.pl\n\n[Follow me on twitter.](http://twitter.com/merowing_)\n\n[Check-out my blog](http://merowing.info) or [GitHub profile](https://github.com/krzysztofzablocki) for more cool stuff.\n\n## Cool usages\n[Check out AudioKit examples using playgrounds](http://audiokit.io/playgrounds)\n\n#### Attribution\n\nSceneKit example code has been taken from [David Ronnqvist](http://ronnqvi.st/book/) upcoming SceneKit book, recommended.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkrzysztofzablocki%2FPlaygrounds","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkrzysztofzablocki%2FPlaygrounds","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkrzysztofzablocki%2FPlaygrounds/lists"}