{"id":20399531,"url":"https://github.com/appsquickly/nanoframe","last_synced_at":"2025-08-02T21:34:15.007Z","repository":{"id":62448813,"uuid":"43931357","full_name":"appsquickly/NanoFrame","owner":"appsquickly","description":"Getting back to basics with minimal frame-based layout and UIKit utils. ","archived":false,"fork":false,"pushed_at":"2021-08-04T00:47:00.000Z","size":614,"stargazers_count":10,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-07-18T07:41:32.787Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/appsquickly.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-10-09T03:44:00.000Z","updated_at":"2024-06-01T08:55:57.000Z","dependencies_parsed_at":"2022-11-01T23:17:00.448Z","dependency_job_id":null,"html_url":"https://github.com/appsquickly/NanoFrame","commit_stats":null,"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/appsquickly/NanoFrame","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appsquickly%2FNanoFrame","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appsquickly%2FNanoFrame/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appsquickly%2FNanoFrame/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appsquickly%2FNanoFrame/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/appsquickly","download_url":"https://codeload.github.com/appsquickly/NanoFrame/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appsquickly%2FNanoFrame/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268456845,"owners_count":24253298,"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","status":"online","status_checked_at":"2025-08-02T02:00:12.353Z","response_time":74,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":[],"created_at":"2024-11-15T04:29:45.320Z","updated_at":"2025-08-02T21:34:14.982Z","avatar_url":"https://github.com/appsquickly.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# NanoFrame\n\nGetting back to basics with minimal frame-based layout and UIKit utils. (aka auto-layout without using auto-layout). \n\n-----\n\n## Why? \n\n\"Everything should be made as simple as possible, but no simpler\". This quote, attributed to Albert Einstein doesn't just apply to solving the deepest mysteries in science. We can use it as a guide-post in software development too. In fact \"simplicity\" among \"communication\", \"feedback\", \"courage\" and \"respect\" is one of the five core values of [Extreme Programming](https://en.wikipedia.org/wiki/Extreme_programming). \n\n## Why not Autolayout? \n\nWe can use it, however it pays to understand the basics first!\n\nBefore there was auto-layout, there were simpler days, with frame-based layout. Autolayout came along to address the issue of laying out views relative to one another, according to various screen sizes. However it is possible (and pretty easy) to lay out views relative to one another, using frame based layouts. We need to understand two fundamental principles; \n\n* A `UIView`'s bounds is the `CGRect` frame, in its _own_ coordinate space. The origin `CGPoint` (0,0) is in the top left corner. \n* A `UIView`'s frame is the CGRect _relative_ to its superview. We can place a `UIView` at some coordinates (100, 50) in some other UIView's frame. \n\n:warning:  _When a view is the root view, its frame and bounds are the same_. \n\nNote the word relative above. We can now lay out views relative to one another using frame and bounds. But just working at a `CGRect` level of abstraction becomes verbose and requires a lot of tedious arithmetic. Computers love tedious arithmetic, so let's add some extension functions to `UIView` for commmon operations. Now we can lay out views as follows: \n\n```swift\noverride func layoutSubviews() {\n    super.layoutSubviews()\n\n    contentView.frame = bounds\n    glowView.frame = contentView.bounds.insetBy(dx: 10, dy: 6)\n    \n    let backgroundColors = [UIColor(hex: 0x424243), UIColor(hex: 0x212121)]\n    glowView.backgroundColor = UIColor(topToBottom: backgroundColors, inFrame: glowView.frame)\n    glowView.layer.shadowPath = UIBezierPath(rect: glowView.bounds).cgPath\n\n    titleLabel.width = glowView.width - 30\n    titleLabel.centerVerticallyInSuperView()\n    titleLabel.x = glowView.x + 10\n\n    starButton.centerVerticallyInSuperView()\n    starButton.right = glowView.right - 20\n}\n```\n\nIn the example above we're laying out the `titleLabel` and `starButton` _relative_ to the superview, just as we do with auto-layout. In fact if we renamed the `x` and `right` computed properties to `leading` and `trailing` it would look even more like auto-layout. \n\nThe difference is that we must do this in the `layoutSubviews()` function (with autolayout we can declare constraints anywhere) however we might consider this a feature. Now we know where to look for layout related code. \n\n## Composition \n\nThere is a reason we come across the word 'compositing' a lot when it comes to graphics. This pattern works well for rendering - start with a root node, and branch out. It can be paralleled. So rather than a complex ancestral heirarchy (`DogView` extends `AnimalView` .... extends `UIView`) we compose complex views from simple pieces. \n\n:warning:  _In fact, we can use [composition over inheritance](https://en.wikipedia.org/wiki/Composition_over_inheritance) as a general rule of thumb._ \n\nThe approach above works well for composition of views. If you see a long `layoutSubviews()` function, there's some evidence that extracting a reusable subview will promote reusablility. Now you can layout this child view relative to the parent. And it becomes simple to reaon about. \n\nYour top-level view's model can deal at the application _domain's_ model, so if for example you have a top-level weather report view, you can feed that view a weather report model. \n\n![Composition Example](https://raw.githubusercontent.com/appsquickly/NanoFrame/master/composite.png) \n\n## Animation \n\nNow that we know how to layout views relative ot one another with composition, we can animate them too. \n\n```swift\nCATransaction.flush()\nUIView.animate(withDuration: 0.28, delay: 0, options: [.curveEaseIn], animations: { [self] in\n  titleLabel.x = starButton.x - 30\n})\n```\n\nWe use a built-in animation curve above. However we could easily apply functions from Newtonian physics to approximate gravity, friction and so forth. \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fappsquickly%2Fnanoframe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fappsquickly%2Fnanoframe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fappsquickly%2Fnanoframe/lists"}