Skip to content
This repository was archived by the owner on Nov 4, 2023. It is now read-only.

Commit cb2ef8a

Browse files
committed
Add Swift/Obj-C UIView subclasses
1 parent eae3b8e commit cb2ef8a

File tree

8 files changed

+231
-10
lines changed

8 files changed

+231
-10
lines changed

FLXSmoothView.h

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//
2+
// FLXSmoothView.h
3+
// SmoothCorners
4+
//
5+
// Created by Felix Lapalme on 2019-03-24.
6+
// Copyright © 2019 Felix Lapalme. All rights reserved.
7+
//
8+
9+
#import <UIKit/UIKit.h>
10+
11+
NS_ASSUME_NONNULL_BEGIN
12+
13+
@interface FLXSmoothView : UIView
14+
15+
@property (nonatomic, assign) BOOL flx_smoothCorners;
16+
17+
@end
18+
19+
NS_ASSUME_NONNULL_END

FLXSmoothView.m

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
//
2+
// FLXSmoothView.m
3+
// SmoothCorners
4+
//
5+
// Created by Felix Lapalme on 2019-03-24.
6+
// Copyright © 2019 Felix Lapalme. All rights reserved.
7+
//
8+
9+
#import "FLXSmoothView.h"
10+
11+
@interface FLXSmoothView()
12+
13+
@property (nonatomic, assign) CGFloat flx_lastSetRadius;
14+
@property (nonatomic, strong) CAShapeLayer *flx_smoothMask;
15+
16+
@end
17+
18+
@implementation FLXSmoothView
19+
20+
-(void)setFlx_smoothCorners:(BOOL)flx_smoothCorners {
21+
_flx_smoothCorners = flx_smoothCorners;
22+
23+
[self flx_updateMaskIfNeeded];
24+
}
25+
26+
- (void)layoutSubviews {
27+
[super layoutSubviews];
28+
29+
if (self.flx_smoothCorners) {
30+
[self flx_updateMaskIfNeeded];
31+
}
32+
}
33+
34+
- (void)flx_updateMaskIfNeeded {
35+
if (self.flx_smoothCorners) {
36+
if (self.flx_smoothMask) {
37+
// If it already exists we only update it if it changed
38+
if (!CGRectEqualToRect(CGPathGetPathBoundingBox(self.flx_smoothMask.path), self.bounds)
39+
|| self.flx_lastSetRadius != self.layer.cornerRadius) {
40+
[self flx_updateMaskShape];
41+
}
42+
} else {
43+
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:self.bounds
44+
cornerRadius:self.layer.cornerRadius];
45+
CAShapeLayer *mask = [CAShapeLayer layer];
46+
mask.path = maskPath.CGPath;
47+
self.layer.mask = mask;
48+
self.flx_smoothMask = mask;
49+
self.flx_lastSetRadius = self.layer.cornerRadius;
50+
51+
[self.layer addObserver:self
52+
forKeyPath:NSStringFromSelector(@selector(cornerRadius))
53+
options:0
54+
context:nil];
55+
}
56+
}
57+
else if (self.flx_smoothMask) {
58+
self.layer.mask = nil;
59+
self.flx_smoothMask = nil;
60+
61+
@try {
62+
[self.layer removeObserver:self forKeyPath:NSStringFromSelector(@selector(cornerRadius))];
63+
}
64+
@catch (NSException * __unused exception) {}
65+
}
66+
}
67+
68+
- (void)flx_updateMaskShape {
69+
self.flx_smoothMask.path = [UIBezierPath bezierPathWithRoundedRect:self.bounds
70+
cornerRadius:self.layer.cornerRadius].CGPath;
71+
self.flx_lastSetRadius = self.layer.cornerRadius;
72+
}
73+
74+
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
75+
if ([keyPath isEqualToString:NSStringFromSelector(@selector(cornerRadius))]) {
76+
[self flx_updateMaskIfNeeded];
77+
}
78+
}
79+
80+
@end

Objective-C Example/SmoothCorners.xcodeproj/project.pbxproj

+6
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
DC118284223D73D100C46A31 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DC118282223D73D100C46A31 /* LaunchScreen.storyboard */; };
1515
DC118287223D73D100C46A31 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DC118286223D73D100C46A31 /* main.m */; };
1616
DC3F464C223F191400877556 /* UIView+SmoothCorners.m in Sources */ = {isa = PBXBuildFile; fileRef = DC3F464B223F191300877556 /* UIView+SmoothCorners.m */; };
17+
DC8F025C225058520040A8EF /* FLXSmoothView.m in Sources */ = {isa = PBXBuildFile; fileRef = DC8F025A225058520040A8EF /* FLXSmoothView.m */; };
1718
/* End PBXBuildFile section */
1819

1920
/* Begin PBXFileReference section */
@@ -29,6 +30,8 @@
2930
DC118286223D73D100C46A31 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
3031
DC3F464A223F191300877556 /* UIView+SmoothCorners.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIView+SmoothCorners.h"; path = "../../UIView+SmoothCorners.h"; sourceTree = "<group>"; };
3132
DC3F464B223F191300877556 /* UIView+SmoothCorners.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIView+SmoothCorners.m"; path = "../../UIView+SmoothCorners.m"; sourceTree = "<group>"; };
33+
DC8F025A225058520040A8EF /* FLXSmoothView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FLXSmoothView.m; path = ../../FLXSmoothView.m; sourceTree = "<group>"; };
34+
DC8F025B225058520040A8EF /* FLXSmoothView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FLXSmoothView.h; path = ../../FLXSmoothView.h; sourceTree = "<group>"; };
3235
/* End PBXFileReference section */
3336

3437
/* Begin PBXFrameworksBuildPhase section */
@@ -67,6 +70,8 @@
6770
DC11827B223D73CE00C46A31 /* ViewController.m */,
6871
DC3F464A223F191300877556 /* UIView+SmoothCorners.h */,
6972
DC3F464B223F191300877556 /* UIView+SmoothCorners.m */,
73+
DC8F025B225058520040A8EF /* FLXSmoothView.h */,
74+
DC8F025A225058520040A8EF /* FLXSmoothView.m */,
7075
DC11827D223D73CE00C46A31 /* Main.storyboard */,
7176
DC118280223D73D100C46A31 /* Assets.xcassets */,
7277
DC118282223D73D100C46A31 /* LaunchScreen.storyboard */,
@@ -150,6 +155,7 @@
150155
DC118287223D73D100C46A31 /* main.m in Sources */,
151156
DC118279223D73CE00C46A31 /* AppDelegate.m in Sources */,
152157
DC3F464C223F191400877556 /* UIView+SmoothCorners.m in Sources */,
158+
DC8F025C225058520040A8EF /* FLXSmoothView.m in Sources */,
153159
);
154160
runOnlyForDeploymentPostprocessing = 0;
155161
};

Objective-C Example/SmoothCorners/ViewController.m

+11-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#import "ViewController.h"
1010
#import "UIView+SmoothCorners.h"
11+
#import "FLXSmoothView.h"
1112

1213
@implementation ViewController
1314

@@ -16,29 +17,37 @@ - (void)viewDidLoad {
1617

1718
UIView *redView = [UIView new];
1819
UIView *yellowView = [UIView new];
20+
FLXSmoothView *blueView = [FLXSmoothView new];
1921

2022
redView.backgroundColor = [[UIColor redColor] colorWithAlphaComponent:0.9];
2123
yellowView.backgroundColor = [[UIColor yellowColor] colorWithAlphaComponent:0.9];
24+
blueView.backgroundColor = [[UIColor blueColor] colorWithAlphaComponent:0.4];
2225

2326
redView.frame = CGRectInset(self.view.bounds, 20, 50);
2427
yellowView.frame = redView.frame;
28+
blueView.frame = redView.frame;
2529

2630
[self.view addSubview:redView];
2731
[self.view addSubview:yellowView];
32+
[self.view addSubview:blueView];
2833

2934
CGFloat radius = 90;
3035

3136
// red view with regular corners
3237
redView.layer.cornerRadius = radius;
3338

34-
// yellow view with smooth corners
39+
// yellow view with smooth corners (using UIView category)
3540
yellowView.flx_continuousCorners = YES;
3641
yellowView.layer.cornerRadius = radius;
3742

43+
// blue view (UIView subclass)
44+
blueView.flx_smoothCorners = YES;
45+
blueView.layer.cornerRadius = radius;
46+
3847
UILabel *explanationLabel = [UILabel new];
3948
explanationLabel.frame = yellowView.frame;
4049
explanationLabel.numberOfLines = 0;
41-
explanationLabel.text = @"Red = regular rounded corners\nYellow = smooth rounded corners";
50+
explanationLabel.text = @"Red = regular rounded corners\nBeige = smooth rounded corners";
4251
[self.view addSubview:explanationLabel];
4352
}
4453

README.md

+30-6
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,42 @@ An easy way to enable [smooth corners](https://hackernoon.com/apples-icons-have-
44
<image height=800 src="https://dsc.cloud/felix/Simulator-Screen-Shot-iPhone-X-2019-03-17-at-20.40.05.png">
55

66
## How to use
7-
### Objective-C
7+
8+
### Swift
9+
#### As a UIView subclass:
10+
Add `SmoothView.swift` to your project
811
```
9-
myView.layer.cornerRadius = 50;
10-
myView.flx_continuousCorners = YES;
12+
let myView = SmoothView()
13+
myView.flx_smoothCorners = true
14+
myView.layer.cornerRadius = 50
1115
```
16+
(Or change the base class of your main `UIView` subclass from `UIView` to `SmoothView`)
1217

13-
### Swift
18+
#### As a UIView category (that swizzles layoutSubviews):
19+
Add `UIView+SmoothCorners.h` and `UIView+SmoothCorners.m` to your project
20+
Add `#import "UIView+SmoothCorners.h"` to your bridging header
1421
```
1522
myView.layer.cornerRadius = 50
1623
myView.flx_continuousCorners = true
1724
```
1825

26+
### Objective-C
27+
#### As a UIView subclass:
28+
Add `FLXSmoothView.h` and `FLXSmoothView.m` to your project
29+
```
30+
FLXSmoothView *myView = FLXSmoothView()
31+
myView.flx_smoothCorners = YES;
32+
myView.layer.cornerRadius = 50;
33+
```
34+
(Or change the base class of your main `UIView` subclass from `UIView` to `FLXSmoothView`)
35+
36+
#### As a UIView category (that swizzles layoutSubviews):
37+
Add `UIView+SmoothCorners.h` and `UIView+SmoothCorners.m` to your project
38+
```
39+
myView.layer.cornerRadius = 50;
40+
myView.flx_continuousCorners = YES;
41+
```
42+
1943
## Shortcomings
20-
- To make things simple, it's a UIView category and I'm not observing the layer's corner radius changes, so make sure you set the corner radius before a layout pass or setting `flx_continuousCorners` to `true`.
21-
- No cocoapods or carthage support yet
44+
- To make things simple for the category approach, I'm not observing the layer's corner radius changes. So make sure the corner radius is set before a layout pass or before setting `flx_continuousCorners` to `true`.
45+
- No cocoapods or carthage support yet

SmoothView.swift

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
//
2+
// SmoothView.swift
3+
// SmoothCorners
4+
//
5+
// Created by Felix Lapalme on 2019-03-24.
6+
// Copyright © 2019 Felix Lapalme. All rights reserved.
7+
//
8+
9+
import UIKit
10+
11+
class SmoothView: UIView {
12+
13+
private var flx_lastSetRadius : CGFloat = 0
14+
private var flx_smoothMask : CAShapeLayer?
15+
16+
var flx_smoothCorners: Bool = false {
17+
didSet {
18+
updateMaskIfNeeded()
19+
}
20+
}
21+
22+
override func layoutSubviews() {
23+
super.layoutSubviews()
24+
25+
if (flx_continuousCorners) {
26+
updateMaskIfNeeded()
27+
}
28+
}
29+
30+
func updateMaskIfNeeded() {
31+
if (flx_smoothCorners) {
32+
if let maskBounds = flx_smoothMask?.path?.boundingBoxOfPath {
33+
if (!bounds.equalTo(maskBounds)
34+
|| flx_lastSetRadius != layer.cornerRadius) {
35+
updateMaskShape()
36+
}
37+
} else {
38+
let path = UIBezierPath(roundedRect: bounds,
39+
cornerRadius: layer.cornerRadius)
40+
let mask = CAShapeLayer()
41+
mask.path = path.cgPath
42+
layer.mask = mask
43+
flx_smoothMask = mask
44+
flx_lastSetRadius = layer.cornerRadius
45+
46+
layer.addObserver(self,
47+
forKeyPath: NSStringFromSelector(#selector(getter: CALayer.cornerRadius)),
48+
options: [],
49+
context: nil)
50+
}
51+
}
52+
else if (flx_smoothMask != nil) {
53+
layer.mask = nil
54+
flx_smoothMask = nil
55+
56+
layer.removeObserver(self, forKeyPath: NSStringFromSelector(#selector(getter: CALayer.cornerRadius)))
57+
}
58+
}
59+
60+
func updateMaskShape() {
61+
flx_smoothMask?.path = UIBezierPath(roundedRect: bounds,
62+
cornerRadius: layer.cornerRadius).cgPath
63+
flx_lastSetRadius = layer.cornerRadius
64+
}
65+
66+
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
67+
if (keyPath == NSStringFromSelector(#selector(getter: CALayer.cornerRadius))) {
68+
updateMaskIfNeeded()
69+
}
70+
}
71+
}

Swift Example/SmoothCorners.xcodeproj/project.pbxproj

+4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
DC3F4661223F1B2500877556 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DC3F4660223F1B2500877556 /* Assets.xcassets */; };
1414
DC3F4664223F1B2500877556 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DC3F4662223F1B2500877556 /* LaunchScreen.storyboard */; };
1515
DC3F466E223F1B5F00877556 /* UIView+SmoothCorners.m in Sources */ = {isa = PBXBuildFile; fileRef = DC3F466C223F1B5F00877556 /* UIView+SmoothCorners.m */; };
16+
DCC5B13422499C05002E3D5F /* SmoothView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCC5B13322499C05002E3D5F /* SmoothView.swift */; };
1617
/* End PBXBuildFile section */
1718

1819
/* Begin PBXFileReference section */
@@ -26,6 +27,7 @@
2627
DC3F466B223F1B5E00877556 /* SmoothCorners-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SmoothCorners-Bridging-Header.h"; sourceTree = "<group>"; };
2728
DC3F466C223F1B5F00877556 /* UIView+SmoothCorners.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIView+SmoothCorners.m"; path = "../../UIView+SmoothCorners.m"; sourceTree = "<group>"; };
2829
DC3F466D223F1B5F00877556 /* UIView+SmoothCorners.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIView+SmoothCorners.h"; path = "../../UIView+SmoothCorners.h"; sourceTree = "<group>"; };
30+
DCC5B13322499C05002E3D5F /* SmoothView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SmoothView.swift; path = ../../SmoothView.swift; sourceTree = "<group>"; };
2931
/* End PBXFileReference section */
3032

3133
/* Begin PBXFrameworksBuildPhase section */
@@ -60,6 +62,7 @@
6062
children = (
6163
DC3F4659223F1B2300877556 /* AppDelegate.swift */,
6264
DC3F465B223F1B2300877556 /* ViewController.swift */,
65+
DCC5B13322499C05002E3D5F /* SmoothView.swift */,
6366
DC3F465D223F1B2300877556 /* Main.storyboard */,
6467
DC3F466D223F1B5F00877556 /* UIView+SmoothCorners.h */,
6568
DC3F466C223F1B5F00877556 /* UIView+SmoothCorners.m */,
@@ -143,6 +146,7 @@
143146
isa = PBXSourcesBuildPhase;
144147
buildActionMask = 2147483647;
145148
files = (
149+
DCC5B13422499C05002E3D5F /* SmoothView.swift in Sources */,
146150
DC3F466E223F1B5F00877556 /* UIView+SmoothCorners.m in Sources */,
147151
DC3F465C223F1B2300877556 /* ViewController.swift in Sources */,
148152
DC3F465A223F1B2300877556 /* AppDelegate.swift in Sources */,

Swift Example/SmoothCorners/ViewController.swift

+10-2
Original file line numberDiff line numberDiff line change
@@ -15,29 +15,37 @@ class ViewController: UIViewController {
1515
// Do any additional setup after loading the view.
1616
let redView = UIView()
1717
let yellowView = UIView()
18+
let blueView = SmoothView()
1819

1920
redView.backgroundColor = UIColor.red.withAlphaComponent(0.9)
2021
yellowView.backgroundColor = UIColor.yellow.withAlphaComponent(0.9)
22+
blueView.backgroundColor = UIColor.blue.withAlphaComponent(0.4)
2123

2224
redView.frame = view.bounds.insetBy(dx: 20, dy: 50)
2325
yellowView.frame = redView.frame
26+
blueView.frame = redView.frame
2427

2528
view.addSubview(redView)
2629
view.addSubview(yellowView)
30+
view.addSubview(blueView)
2731

2832
let radius : CGFloat = 90.0
2933

3034
// red view with regular corners
3135
redView.layer.cornerRadius = radius
3236

33-
// yellow view with smooth corners
37+
// using category/extension
3438
yellowView.flx_continuousCorners = true
3539
yellowView.layer.cornerRadius = radius
3640

41+
// using subclass
42+
blueView.flx_smoothCorners = true
43+
blueView.layer.cornerRadius = radius
44+
3745
let explanationLabel = UILabel()
3846
explanationLabel.frame = yellowView.frame
3947
explanationLabel.numberOfLines = 0
40-
explanationLabel.text = "Red = regular rounded corners\nYellow = smooth rounded corners"
48+
explanationLabel.text = "Red = regular rounded corners\nBeige = smooth rounded corners"
4149
view.addSubview(explanationLabel)
4250
}
4351
}

0 commit comments

Comments
 (0)