├── .swift-version
├── IBLayoutConstraint.podspec
├── LICENSE
├── LayoutConstraint-Example.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ ├── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
│ └── xcuserdata
│ │ └── maksim.kurpa.xcuserdatad
│ │ └── UserInterfaceState.xcuserstate
└── xcuserdata
│ └── maksim.kurpa.xcuserdatad
│ ├── xcdebugger
│ └── Breakpoints_v2.xcbkptlist
│ └── xcschemes
│ └── xcschememanagement.plist
├── LayoutConstraint-Example
├── AppDelegate.swift
├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ └── Contents.json
├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
├── Info.plist
├── SceneDelegate.swift
└── ViewController.swift
├── README.md
├── Sources
├── IBLayoutConstraint.swift
├── IBMultiplierLayoutConstraint.swift
├── UIDevice+Extension.swift
├── UIScreen+Extension.swift
└── UIView+Extension.swift
└── docs
├── ib_screen_1.png
└── ib_screen_2.png
/.swift-version:
--------------------------------------------------------------------------------
1 | 5.0
2 |
--------------------------------------------------------------------------------
/IBLayoutConstraint.podspec:
--------------------------------------------------------------------------------
1 |
2 | Pod::Spec.new do |s|
3 | s.name = 'IBLayoutConstraint'
4 | s.version = '1.0.0'
5 | s.summary = 'Configure constraints for each device in Interface Builder with IBLayoutConstraint.'
6 | s.license = { :type => 'MIT', :file => 'LICENSE' }
7 | s.author = { 'Maks Kurpa' => 'maksim.kurpa@gmail.com' }
8 | s.description = 'Specify constraints for each device separately. Forget about device detection and configuring constraints in code. Do it in Interface Builder with IBLayoutConstraint.'
9 | s.homepage = 'https://github.com/MaksimKurpa/IBLayoutConstraint'
10 | s.source = { :git => 'https://github.com/MaksimKurpa/IBLayoutConstraint.git', :branch => 'master',:tag => s.version.to_s }
11 | s.social_media_url = 'https://www.facebook.com/maksim.kurpa'
12 | s.source_files = 'Sources/*.{swift}'
13 | s.requires_arc = true
14 | s.swift_version = '5.0'
15 | s.ios.deployment_target = '11.0'
16 | s.ios.framework = 'UIKit'
17 | end
18 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Maksim Kurpa
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/LayoutConstraint-Example.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 2885B767243A2BB100A0B1A4 /* UIDevice+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2885B762243A2BB100A0B1A4 /* UIDevice+Extension.swift */; };
11 | 2885B768243A2BB100A0B1A4 /* IBLayoutConstraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2885B763243A2BB100A0B1A4 /* IBLayoutConstraint.swift */; };
12 | 2885B769243A2BB100A0B1A4 /* UIView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2885B764243A2BB100A0B1A4 /* UIView+Extension.swift */; };
13 | 2885B76A243A2BB100A0B1A4 /* UIScreen+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2885B765243A2BB100A0B1A4 /* UIScreen+Extension.swift */; };
14 | 2885B76B243A2BB100A0B1A4 /* IBMultiplierLayoutConstraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2885B766243A2BB100A0B1A4 /* IBMultiplierLayoutConstraint.swift */; };
15 | 2885B776243A2ED600A0B1A4 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2885B76D243A2ED600A0B1A4 /* ViewController.swift */; };
16 | 2885B777243A2ED600A0B1A4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2885B76E243A2ED600A0B1A4 /* Assets.xcassets */; };
17 | 2885B778243A2ED600A0B1A4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2885B76F243A2ED600A0B1A4 /* LaunchScreen.storyboard */; };
18 | 2885B779243A2ED600A0B1A4 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2885B771243A2ED600A0B1A4 /* Main.storyboard */; };
19 | 2885B77A243A2ED600A0B1A4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2885B773243A2ED600A0B1A4 /* AppDelegate.swift */; };
20 | 2885B77C243A2ED600A0B1A4 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2885B775243A2ED600A0B1A4 /* SceneDelegate.swift */; };
21 | /* End PBXBuildFile section */
22 |
23 | /* Begin PBXFileReference section */
24 | 2885B73F2437C12100A0B1A4 /* LayoutConstraint-Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "LayoutConstraint-Example.app"; sourceTree = BUILT_PRODUCTS_DIR; };
25 | 2885B762243A2BB100A0B1A4 /* UIDevice+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIDevice+Extension.swift"; sourceTree = ""; };
26 | 2885B763243A2BB100A0B1A4 /* IBLayoutConstraint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IBLayoutConstraint.swift; sourceTree = ""; };
27 | 2885B764243A2BB100A0B1A4 /* UIView+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Extension.swift"; sourceTree = ""; };
28 | 2885B765243A2BB100A0B1A4 /* UIScreen+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIScreen+Extension.swift"; sourceTree = ""; };
29 | 2885B766243A2BB100A0B1A4 /* IBMultiplierLayoutConstraint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IBMultiplierLayoutConstraint.swift; sourceTree = ""; };
30 | 2885B76D243A2ED600A0B1A4 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
31 | 2885B76E243A2ED600A0B1A4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
32 | 2885B770243A2ED600A0B1A4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
33 | 2885B772243A2ED600A0B1A4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
34 | 2885B773243A2ED600A0B1A4 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
35 | 2885B774243A2ED600A0B1A4 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
36 | 2885B775243A2ED600A0B1A4 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
37 | /* End PBXFileReference section */
38 |
39 | /* Begin PBXFrameworksBuildPhase section */
40 | 2885B73C2437C12100A0B1A4 /* Frameworks */ = {
41 | isa = PBXFrameworksBuildPhase;
42 | buildActionMask = 2147483647;
43 | files = (
44 | );
45 | runOnlyForDeploymentPostprocessing = 0;
46 | };
47 | /* End PBXFrameworksBuildPhase section */
48 |
49 | /* Begin PBXGroup section */
50 | 2885B7362437C12100A0B1A4 = {
51 | isa = PBXGroup;
52 | children = (
53 | 2885B761243A2BB100A0B1A4 /* Sources */,
54 | 2885B76C243A2ED600A0B1A4 /* LayoutConstraint-Example */,
55 | 2885B7402437C12100A0B1A4 /* Products */,
56 | );
57 | sourceTree = "";
58 | };
59 | 2885B7402437C12100A0B1A4 /* Products */ = {
60 | isa = PBXGroup;
61 | children = (
62 | 2885B73F2437C12100A0B1A4 /* LayoutConstraint-Example.app */,
63 | );
64 | name = Products;
65 | sourceTree = "";
66 | };
67 | 2885B761243A2BB100A0B1A4 /* Sources */ = {
68 | isa = PBXGroup;
69 | children = (
70 | 2885B762243A2BB100A0B1A4 /* UIDevice+Extension.swift */,
71 | 2885B763243A2BB100A0B1A4 /* IBLayoutConstraint.swift */,
72 | 2885B764243A2BB100A0B1A4 /* UIView+Extension.swift */,
73 | 2885B765243A2BB100A0B1A4 /* UIScreen+Extension.swift */,
74 | 2885B766243A2BB100A0B1A4 /* IBMultiplierLayoutConstraint.swift */,
75 | );
76 | path = Sources;
77 | sourceTree = "";
78 | };
79 | 2885B76C243A2ED600A0B1A4 /* LayoutConstraint-Example */ = {
80 | isa = PBXGroup;
81 | children = (
82 | 2885B76D243A2ED600A0B1A4 /* ViewController.swift */,
83 | 2885B76E243A2ED600A0B1A4 /* Assets.xcassets */,
84 | 2885B76F243A2ED600A0B1A4 /* LaunchScreen.storyboard */,
85 | 2885B771243A2ED600A0B1A4 /* Main.storyboard */,
86 | 2885B773243A2ED600A0B1A4 /* AppDelegate.swift */,
87 | 2885B774243A2ED600A0B1A4 /* Info.plist */,
88 | 2885B775243A2ED600A0B1A4 /* SceneDelegate.swift */,
89 | );
90 | path = "LayoutConstraint-Example";
91 | sourceTree = SOURCE_ROOT;
92 | };
93 | /* End PBXGroup section */
94 |
95 | /* Begin PBXNativeTarget section */
96 | 2885B73E2437C12100A0B1A4 /* LayoutConstraint-Example */ = {
97 | isa = PBXNativeTarget;
98 | buildConfigurationList = 2885B7532437C12300A0B1A4 /* Build configuration list for PBXNativeTarget "LayoutConstraint-Example" */;
99 | buildPhases = (
100 | 2885B73B2437C12100A0B1A4 /* Sources */,
101 | 2885B73C2437C12100A0B1A4 /* Frameworks */,
102 | 2885B73D2437C12100A0B1A4 /* Resources */,
103 | );
104 | buildRules = (
105 | );
106 | dependencies = (
107 | );
108 | name = "LayoutConstraint-Example";
109 | productName = LayoutConstraint;
110 | productReference = 2885B73F2437C12100A0B1A4 /* LayoutConstraint-Example.app */;
111 | productType = "com.apple.product-type.application";
112 | };
113 | /* End PBXNativeTarget section */
114 |
115 | /* Begin PBXProject section */
116 | 2885B7372437C12100A0B1A4 /* Project object */ = {
117 | isa = PBXProject;
118 | attributes = {
119 | LastSwiftUpdateCheck = 1140;
120 | LastUpgradeCheck = 1140;
121 | ORGANIZATIONNAME = "Maks Kurpa";
122 | TargetAttributes = {
123 | 2885B73E2437C12100A0B1A4 = {
124 | CreatedOnToolsVersion = 11.4;
125 | };
126 | };
127 | };
128 | buildConfigurationList = 2885B73A2437C12100A0B1A4 /* Build configuration list for PBXProject "LayoutConstraint-Example" */;
129 | compatibilityVersion = "Xcode 9.3";
130 | developmentRegion = en;
131 | hasScannedForEncodings = 0;
132 | knownRegions = (
133 | en,
134 | Base,
135 | );
136 | mainGroup = 2885B7362437C12100A0B1A4;
137 | productRefGroup = 2885B7402437C12100A0B1A4 /* Products */;
138 | projectDirPath = "";
139 | projectRoot = "";
140 | targets = (
141 | 2885B73E2437C12100A0B1A4 /* LayoutConstraint-Example */,
142 | );
143 | };
144 | /* End PBXProject section */
145 |
146 | /* Begin PBXResourcesBuildPhase section */
147 | 2885B73D2437C12100A0B1A4 /* Resources */ = {
148 | isa = PBXResourcesBuildPhase;
149 | buildActionMask = 2147483647;
150 | files = (
151 | 2885B779243A2ED600A0B1A4 /* Main.storyboard in Resources */,
152 | 2885B777243A2ED600A0B1A4 /* Assets.xcassets in Resources */,
153 | 2885B778243A2ED600A0B1A4 /* LaunchScreen.storyboard in Resources */,
154 | );
155 | runOnlyForDeploymentPostprocessing = 0;
156 | };
157 | /* End PBXResourcesBuildPhase section */
158 |
159 | /* Begin PBXSourcesBuildPhase section */
160 | 2885B73B2437C12100A0B1A4 /* Sources */ = {
161 | isa = PBXSourcesBuildPhase;
162 | buildActionMask = 2147483647;
163 | files = (
164 | 2885B776243A2ED600A0B1A4 /* ViewController.swift in Sources */,
165 | 2885B769243A2BB100A0B1A4 /* UIView+Extension.swift in Sources */,
166 | 2885B768243A2BB100A0B1A4 /* IBLayoutConstraint.swift in Sources */,
167 | 2885B76B243A2BB100A0B1A4 /* IBMultiplierLayoutConstraint.swift in Sources */,
168 | 2885B77A243A2ED600A0B1A4 /* AppDelegate.swift in Sources */,
169 | 2885B767243A2BB100A0B1A4 /* UIDevice+Extension.swift in Sources */,
170 | 2885B76A243A2BB100A0B1A4 /* UIScreen+Extension.swift in Sources */,
171 | 2885B77C243A2ED600A0B1A4 /* SceneDelegate.swift in Sources */,
172 | );
173 | runOnlyForDeploymentPostprocessing = 0;
174 | };
175 | /* End PBXSourcesBuildPhase section */
176 |
177 | /* Begin PBXVariantGroup section */
178 | 2885B76F243A2ED600A0B1A4 /* LaunchScreen.storyboard */ = {
179 | isa = PBXVariantGroup;
180 | children = (
181 | 2885B770243A2ED600A0B1A4 /* Base */,
182 | );
183 | name = LaunchScreen.storyboard;
184 | sourceTree = "";
185 | };
186 | 2885B771243A2ED600A0B1A4 /* Main.storyboard */ = {
187 | isa = PBXVariantGroup;
188 | children = (
189 | 2885B772243A2ED600A0B1A4 /* Base */,
190 | );
191 | name = Main.storyboard;
192 | sourceTree = "";
193 | };
194 | /* End PBXVariantGroup section */
195 |
196 | /* Begin XCBuildConfiguration section */
197 | 2885B7512437C12300A0B1A4 /* Debug */ = {
198 | isa = XCBuildConfiguration;
199 | buildSettings = {
200 | ALWAYS_SEARCH_USER_PATHS = NO;
201 | CLANG_ANALYZER_NONNULL = YES;
202 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
203 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
204 | CLANG_CXX_LIBRARY = "libc++";
205 | CLANG_ENABLE_MODULES = YES;
206 | CLANG_ENABLE_OBJC_ARC = YES;
207 | CLANG_ENABLE_OBJC_WEAK = YES;
208 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
209 | CLANG_WARN_BOOL_CONVERSION = YES;
210 | CLANG_WARN_COMMA = YES;
211 | CLANG_WARN_CONSTANT_CONVERSION = YES;
212 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
213 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
214 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
215 | CLANG_WARN_EMPTY_BODY = YES;
216 | CLANG_WARN_ENUM_CONVERSION = YES;
217 | CLANG_WARN_INFINITE_RECURSION = YES;
218 | CLANG_WARN_INT_CONVERSION = YES;
219 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
220 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
221 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
222 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
223 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
224 | CLANG_WARN_STRICT_PROTOTYPES = YES;
225 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
226 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
227 | CLANG_WARN_UNREACHABLE_CODE = YES;
228 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
229 | COPY_PHASE_STRIP = NO;
230 | DEBUG_INFORMATION_FORMAT = dwarf;
231 | ENABLE_STRICT_OBJC_MSGSEND = YES;
232 | ENABLE_TESTABILITY = YES;
233 | GCC_C_LANGUAGE_STANDARD = gnu11;
234 | GCC_DYNAMIC_NO_PIC = NO;
235 | GCC_NO_COMMON_BLOCKS = YES;
236 | GCC_OPTIMIZATION_LEVEL = 0;
237 | GCC_PREPROCESSOR_DEFINITIONS = (
238 | "DEBUG=1",
239 | "$(inherited)",
240 | );
241 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
242 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
243 | GCC_WARN_UNDECLARED_SELECTOR = YES;
244 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
245 | GCC_WARN_UNUSED_FUNCTION = YES;
246 | GCC_WARN_UNUSED_VARIABLE = YES;
247 | IPHONEOS_DEPLOYMENT_TARGET = 13.4;
248 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
249 | MTL_FAST_MATH = YES;
250 | ONLY_ACTIVE_ARCH = YES;
251 | SDKROOT = iphoneos;
252 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
253 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
254 | };
255 | name = Debug;
256 | };
257 | 2885B7522437C12300A0B1A4 /* Release */ = {
258 | isa = XCBuildConfiguration;
259 | buildSettings = {
260 | ALWAYS_SEARCH_USER_PATHS = NO;
261 | CLANG_ANALYZER_NONNULL = YES;
262 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
263 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
264 | CLANG_CXX_LIBRARY = "libc++";
265 | CLANG_ENABLE_MODULES = YES;
266 | CLANG_ENABLE_OBJC_ARC = YES;
267 | CLANG_ENABLE_OBJC_WEAK = YES;
268 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
269 | CLANG_WARN_BOOL_CONVERSION = YES;
270 | CLANG_WARN_COMMA = YES;
271 | CLANG_WARN_CONSTANT_CONVERSION = YES;
272 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
273 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
274 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
275 | CLANG_WARN_EMPTY_BODY = YES;
276 | CLANG_WARN_ENUM_CONVERSION = YES;
277 | CLANG_WARN_INFINITE_RECURSION = YES;
278 | CLANG_WARN_INT_CONVERSION = YES;
279 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
280 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
281 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
282 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
283 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
284 | CLANG_WARN_STRICT_PROTOTYPES = YES;
285 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
286 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
287 | CLANG_WARN_UNREACHABLE_CODE = YES;
288 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
289 | COPY_PHASE_STRIP = NO;
290 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
291 | ENABLE_NS_ASSERTIONS = NO;
292 | ENABLE_STRICT_OBJC_MSGSEND = YES;
293 | GCC_C_LANGUAGE_STANDARD = gnu11;
294 | GCC_NO_COMMON_BLOCKS = YES;
295 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
296 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
297 | GCC_WARN_UNDECLARED_SELECTOR = YES;
298 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
299 | GCC_WARN_UNUSED_FUNCTION = YES;
300 | GCC_WARN_UNUSED_VARIABLE = YES;
301 | IPHONEOS_DEPLOYMENT_TARGET = 13.4;
302 | MTL_ENABLE_DEBUG_INFO = NO;
303 | MTL_FAST_MATH = YES;
304 | SDKROOT = iphoneos;
305 | SWIFT_COMPILATION_MODE = wholemodule;
306 | SWIFT_OPTIMIZATION_LEVEL = "-O";
307 | VALIDATE_PRODUCT = YES;
308 | };
309 | name = Release;
310 | };
311 | 2885B7542437C12300A0B1A4 /* Debug */ = {
312 | isa = XCBuildConfiguration;
313 | buildSettings = {
314 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
315 | CODE_SIGN_STYLE = Manual;
316 | DEVELOPMENT_TEAM = "";
317 | INFOPLIST_FILE = "$(SRCROOT)/LayoutConstraint-Example/Info.plist";
318 | LD_RUNPATH_SEARCH_PATHS = (
319 | "$(inherited)",
320 | "@executable_path/Frameworks",
321 | );
322 | PRODUCT_BUNDLE_IDENTIFIER = com.yourcompany.layotconstraint;
323 | PRODUCT_NAME = "$(TARGET_NAME)";
324 | PROVISIONING_PROFILE_SPECIFIER = "";
325 | SWIFT_VERSION = 5.0;
326 | TARGETED_DEVICE_FAMILY = "1,2";
327 | };
328 | name = Debug;
329 | };
330 | 2885B7552437C12300A0B1A4 /* Release */ = {
331 | isa = XCBuildConfiguration;
332 | buildSettings = {
333 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
334 | CODE_SIGN_STYLE = Manual;
335 | DEVELOPMENT_TEAM = "";
336 | INFOPLIST_FILE = "$(SRCROOT)/LayoutConstraint-Example/Info.plist";
337 | LD_RUNPATH_SEARCH_PATHS = (
338 | "$(inherited)",
339 | "@executable_path/Frameworks",
340 | );
341 | PRODUCT_BUNDLE_IDENTIFIER = com.yourcompany.layotconstraint;
342 | PRODUCT_NAME = "$(TARGET_NAME)";
343 | PROVISIONING_PROFILE_SPECIFIER = "";
344 | SWIFT_VERSION = 5.0;
345 | TARGETED_DEVICE_FAMILY = "1,2";
346 | };
347 | name = Release;
348 | };
349 | /* End XCBuildConfiguration section */
350 |
351 | /* Begin XCConfigurationList section */
352 | 2885B73A2437C12100A0B1A4 /* Build configuration list for PBXProject "LayoutConstraint-Example" */ = {
353 | isa = XCConfigurationList;
354 | buildConfigurations = (
355 | 2885B7512437C12300A0B1A4 /* Debug */,
356 | 2885B7522437C12300A0B1A4 /* Release */,
357 | );
358 | defaultConfigurationIsVisible = 0;
359 | defaultConfigurationName = Release;
360 | };
361 | 2885B7532437C12300A0B1A4 /* Build configuration list for PBXNativeTarget "LayoutConstraint-Example" */ = {
362 | isa = XCConfigurationList;
363 | buildConfigurations = (
364 | 2885B7542437C12300A0B1A4 /* Debug */,
365 | 2885B7552437C12300A0B1A4 /* Release */,
366 | );
367 | defaultConfigurationIsVisible = 0;
368 | defaultConfigurationName = Release;
369 | };
370 | /* End XCConfigurationList section */
371 | };
372 | rootObject = 2885B7372437C12100A0B1A4 /* Project object */;
373 | }
374 |
--------------------------------------------------------------------------------
/LayoutConstraint-Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/LayoutConstraint-Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/LayoutConstraint-Example.xcodeproj/project.xcworkspace/xcuserdata/maksim.kurpa.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaksimKurpa/IBLayoutConstraint/af3471701b2dcbe4d3fa85f60c6411a9e28d7c70/LayoutConstraint-Example.xcodeproj/project.xcworkspace/xcuserdata/maksim.kurpa.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/LayoutConstraint-Example.xcodeproj/xcuserdata/maksim.kurpa.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
9 |
21 |
22 |
23 |
25 |
37 |
38 |
39 |
41 |
53 |
54 |
55 |
57 |
69 |
70 |
71 |
73 |
85 |
86 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/LayoutConstraint-Example.xcodeproj/xcuserdata/maksim.kurpa.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | LayoutConstraint-Example.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 | LayoutConstraint.xcscheme_^#shared#^_
13 |
14 | orderHint
15 | 0
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/LayoutConstraint-Example/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // IBLayoutConstraint
4 | //
5 | // Created by Maks Kurpa on 4/3/20.
6 | // Copyright © 2020 Maks Kurpa. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 |
15 |
16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
17 | // Override point for customization after application launch.
18 | return true
19 | }
20 |
21 | // MARK: UISceneSession Lifecycle
22 |
23 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
24 | // Called when a new scene session is being created.
25 | // Use this method to select a configuration to create the new scene with.
26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
27 | }
28 |
29 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
30 | // Called when the user discards a scene session.
31 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
32 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
33 | }
34 |
35 |
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/LayoutConstraint-Example/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | }
93 | ],
94 | "info" : {
95 | "author" : "xcode",
96 | "version" : 1
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/LayoutConstraint-Example/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/LayoutConstraint-Example/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/LayoutConstraint-Example/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/LayoutConstraint-Example/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIApplicationSceneManifest
24 |
25 | UIApplicationSupportsMultipleScenes
26 |
27 | UISceneConfigurations
28 |
29 | UIWindowSceneSessionRoleApplication
30 |
31 |
32 | UISceneConfigurationName
33 | Default Configuration
34 | UISceneDelegateClassName
35 | $(PRODUCT_MODULE_NAME).SceneDelegate
36 | UISceneStoryboardFile
37 | Main
38 |
39 |
40 |
41 |
42 | UILaunchStoryboardName
43 | LaunchScreen
44 | UIMainStoryboardFile
45 | Main
46 | UIRequiredDeviceCapabilities
47 |
48 | armv7
49 |
50 | UISupportedInterfaceOrientations
51 |
52 | UIInterfaceOrientationPortrait
53 | UIInterfaceOrientationLandscapeLeft
54 | UIInterfaceOrientationLandscapeRight
55 |
56 | UISupportedInterfaceOrientations~ipad
57 |
58 | UIInterfaceOrientationPortrait
59 | UIInterfaceOrientationPortraitUpsideDown
60 | UIInterfaceOrientationLandscapeLeft
61 | UIInterfaceOrientationLandscapeRight
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/LayoutConstraint-Example/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // IBLayoutConstraint
4 | //
5 | // Created by Maks Kurpa on 4/3/20.
6 | // Copyright © 2020 Maks Kurpa. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
12 |
13 | var window: UIWindow?
14 |
15 |
16 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
17 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
18 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
19 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
20 | guard let _ = (scene as? UIWindowScene) else { return }
21 | }
22 |
23 | func sceneDidDisconnect(_ scene: UIScene) {
24 | // Called as the scene is being released by the system.
25 | // This occurs shortly after the scene enters the background, or when its session is discarded.
26 | // Release any resources associated with this scene that can be re-created the next time the scene connects.
27 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
28 | }
29 |
30 | func sceneDidBecomeActive(_ scene: UIScene) {
31 | // Called when the scene has moved from an inactive state to an active state.
32 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
33 | }
34 |
35 | func sceneWillResignActive(_ scene: UIScene) {
36 | // Called when the scene will move from an active state to an inactive state.
37 | // This may occur due to temporary interruptions (ex. an incoming phone call).
38 | }
39 |
40 | func sceneWillEnterForeground(_ scene: UIScene) {
41 | // Called as the scene transitions from the background to the foreground.
42 | // Use this method to undo the changes made on entering the background.
43 | }
44 |
45 | func sceneDidEnterBackground(_ scene: UIScene) {
46 | // Called as the scene transitions from the foreground to the background.
47 | // Use this method to save data, release shared resources, and store enough scene-specific state information
48 | // to restore the scene back to its current state.
49 | }
50 |
51 |
52 | }
53 |
54 |
--------------------------------------------------------------------------------
/LayoutConstraint-Example/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // IBLayoutConstraint
4 | //
5 | // Created by Maks Kurpa on 4/3/20.
6 | // Copyright © 2020 Maks Kurpa. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ViewController: UIViewController {
12 |
13 | override func viewDidLoad() {
14 | super.viewDidLoad()
15 | // Do any additional setup after loading the view.
16 | }
17 |
18 |
19 | }
20 |
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # IBLayoutConstraint
2 | 📏 Configure constraints for each device in Interface Builder with IBLayoutConstraint.
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | ## Requirements
13 | IBLayoutConstraint is written in Swift 5.0 and is available on iOS/Mac Catalyst 11.0 or higher.
14 |
15 | ## Installation
16 |
17 | ### Dependency Managers
18 |
19 | CocoaPods
20 |
21 | [CocoaPods](http://cocoapods.org) is a dependency manager for Cocoa projects. You can install it with the following command:
22 |
23 | ```bash
24 | $ gem install cocoapods
25 | ```
26 |
27 | To integrate IBLayoutConstraint into your Xcode project using CocoaPods, specify it in your `Podfile`:
28 |
29 | ```ruby
30 | source 'https://cdn.cocoapods.org/'
31 | platform :ios, '11.0'
32 |
33 | pod 'IBLayoutConstraint'
34 | ```
35 |
36 | Then, run the following command:
37 |
38 | ```bash
39 | $ pod install
40 | ```
41 |
42 |
43 |
44 | ## Introduction
45 |
46 | The main thought of this framework is convinient way to configure constraint for each device in Interface Builder.
47 | Sometimes we need to change `constant`/`multiplier` value for `NSLayoutConstraint` only for specific device, for example, for iPhone 5s (4").
48 | If this `UIView` with layout has special constraint for one device, we should do:
49 |
50 | 1. inheritance from `UIView`
51 | 2. add `outlet` to this view `@IBOutlet weak private var someConstraint: NSLayoutConstraint!`
52 | 3. add some code to change constraint's value for specific device
53 | ```Swift
54 | if UIDevice.current.isIPhone5() {
55 | someConstraint.constant = 290
56 | }
57 | ```
58 |
59 | IBLayoutConstraint will takes responsibility for this cases.
60 |
61 | ## How to use
62 |
63 | (!) Notice: This approach isn't quite obvious, and you should point out it in documentation of your project.
64 |
65 |
66 | 1. Find `constraint` for configuration in Interface Builder View in Xcode.
67 | 2. Go to `Indentity Inspector` for this `constraint` in right-side panel.
68 | 3. Change Class `NSLayoutConstraint` to `IBLayoutConstraint`
69 |
70 | If you want to specify `multiplier` value or `constraint`and `multiplier` toghether, you should use `IBMultiplierLayoutConstraint`.
71 |
72 | 4. Go to `Attributes Inspector` for this `constraint` in right-side panel. You'll see:
73 |
74 | #### IBLayoutConstraint
75 |
76 |
77 |

78 |
79 |
80 | #### IBMultiplierLayoutConstraint
81 |
82 |
83 |

84 |
85 |
86 | 5. Find the screen size in inches that matches your need. You'll see two textfields:
87 |
88 | `Width` - value for landscape mode
89 |
90 | `Height` - value for portrait mode
91 |
92 | Fill the text fields. The necessary value will be applied after initialization and will change accordingly after device rotation.
93 |
94 | ##### Note: You don't need specify values for all sizes,but you need specify values for 'width' and 'height' in one line - it is required (even if you don't support rotation). If values don't specified for some size - IBLayoutConstraint will work as NSLayoutConstraint.
95 |
96 | ## Contributing
97 |
98 | Issues and pull requests are welcome!
99 |
100 | ## Author
101 |
102 | Maksim Kurpa - [@maksim_kurpa](https://twitter.com/maksim_kurpa)
103 |
104 | ## License
105 |
106 | This code is distributed under the terms and conditions of the [MIT license](https://raw.githubusercontent.com/MaksimKurpa/IBLayoutConstraint/master/LICENSE).
107 |
--------------------------------------------------------------------------------
/Sources/IBLayoutConstraint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IBLayoutConstraint.swift
3 | // IBLayoutConstraint
4 | //
5 | // Created by Maks Kurpa on 4/3/20.
6 | // Copyright © 2020 Maks Kurpa. All rights reserved.
7 | //
8 |
9 |
10 | import UIKit
11 |
12 | class IBLayoutConstraint: NSLayoutConstraint {
13 |
14 | internal let orientationNotification = UIDevice.orientationDidChangeNotification
15 |
16 | override func awakeFromNib() {
17 | super.awakeFromNib()
18 | setupObserver()
19 | setup()
20 | }
21 |
22 | func setupObserver() {
23 | NotificationCenter.default.addObserver(self, selector: #selector(orientationDidChange(_:)), name: orientationNotification, object: nil)
24 | }
25 |
26 | @objc func setup() {
27 | if let constantValue = self.constantValue() {
28 | self.constant = constantValue
29 | }
30 | }
31 |
32 | @objc func orientationDidChange(_ note: Notification?) {
33 | performSelector(onMainThread: #selector(setup), with: nil, waitUntilDone: false)
34 | }
35 |
36 | deinit {
37 | NotificationCenter.default.removeObserver(self, name: orientationNotification, object: nil)
38 | }
39 |
40 | @IBInspectable var safeAreaVerticalDistinction: Bool = false {
41 | didSet {
42 | if safeAreaVerticalDistinction {
43 | let additional = UIView.safeAreaIndents.bottom
44 | self.constant = (self.constant + additional)
45 | }
46 | }
47 | }
48 |
49 | internal var inch40: CGSize?
50 | internal var inch47: CGSize?
51 | internal var inch55: CGSize?
52 | internal var inch58: CGSize?
53 | internal var inch61: CGSize?
54 | internal var inch65: CGSize?
55 |
56 | internal var inch79: CGSize?
57 | internal var inch97: CGSize?
58 | internal var inch102: CGSize?
59 | internal var inch105: CGSize?
60 | internal var inch111: CGSize?
61 | internal var inch129: CGSize?
62 |
63 | private func constantValue() -> CGFloat? {
64 | let values: CGSize?
65 | switch UIDevice.currentScreen {
66 | case .inch40: values = inch40
67 | case .inch47: values = inch47
68 | case .inch55: values = inch55
69 | case .inch58: values = inch58
70 | case .inch61: values = inch61
71 | case .inch65: values = inch65
72 | case .inch79: values = inch79
73 | case .inch97: values = inch97
74 | case .inch102: values = inch102
75 | case .inch105: values = inch105
76 | case .inch111: values = inch111
77 | case .inch129: values = inch129
78 | case .unknown: values = CGSize(width: self.constant, height: self.constant)
79 | }
80 |
81 | if UIDevice.isPortrait {
82 | if values?.height != constant, values?.height != 0 {
83 | return values?.height
84 | }
85 | } else {
86 | if values?.width != constant, values?.width != 0 {
87 | return values?.width
88 | }
89 | }
90 | return nil
91 | }
92 |
93 | private func setConstantValue(_ value: CGSize ,for screen: UIDevice.Screen){
94 | switch screen {
95 | case .inch40: inch40 = value
96 | case .inch47: inch47 = value
97 | case .inch55: inch55 = value
98 | case .inch58: inch58 = value
99 | case .inch61: inch61 = value
100 | case .inch65: inch65 = value
101 | case .inch79: inch79 = value
102 | case .inch97: inch97 = value
103 | case .inch102: inch102 = value
104 | case .inch105: inch105 = value
105 | case .inch111: inch111 = value
106 | case .inch129: inch129 = value
107 | case .unknown: break
108 |
109 | }
110 | if screen == UIDevice.currentScreen {
111 | let additional: CGFloat = self.safeAreaVerticalDistinction ? (UIView.safeAreaIndents.bottom) : 0
112 | self.constant = (additional + (UIDevice.isPortrait ? value.height : value.width))
113 | }
114 | }
115 | }
116 |
117 | extension IBLayoutConstraint {
118 |
119 | @IBInspectable var _40ˮ_constant: CGSize {
120 | get { return inch40 ?? CGSize(width: constant, height: constant) }
121 | set { setConstantValue(newValue, for: .inch40) }
122 | }
123 |
124 | @IBInspectable var _47ˮ_constant: CGSize {
125 | get { return inch47 ?? CGSize(width: constant, height: constant) }
126 | set { setConstantValue(newValue, for: .inch47) }
127 | }
128 |
129 | @IBInspectable var _55ˮ_constant: CGSize {
130 | get { return inch55 ?? CGSize(width: constant, height: constant) }
131 | set { setConstantValue(newValue, for: .inch55) }
132 | }
133 |
134 | @IBInspectable var _58ˮ_constant: CGSize {
135 | get { return inch58 ?? CGSize(width: constant, height: constant) }
136 | set { setConstantValue(newValue, for: .inch58) }
137 | }
138 |
139 | @IBInspectable var _61ˮ_constant: CGSize {
140 | get { return inch61 ?? CGSize(width: constant, height: constant) }
141 | set { setConstantValue(newValue, for: .inch61) }
142 | }
143 |
144 | @IBInspectable var _65ˮ_constant: CGSize {
145 | get { return inch65 ?? CGSize(width: constant, height: constant) }
146 | set { setConstantValue(newValue, for: .inch65) }
147 | }
148 |
149 | @IBInspectable var _79ˮ_constant: CGSize {
150 | get { return inch79 ?? CGSize(width: constant, height: constant) }
151 | set { setConstantValue(newValue, for: .inch79) }
152 | }
153 |
154 | @IBInspectable var _97ˮ_constant: CGSize {
155 | get { return inch97 ?? CGSize(width: constant, height: constant) }
156 | set { setConstantValue(newValue, for: .inch97) }
157 | }
158 |
159 | @IBInspectable var _102ˮ_constant: CGSize {
160 | get { return inch102 ?? CGSize(width: constant, height: constant) }
161 | set { setConstantValue(newValue, for: .inch102) }
162 | }
163 |
164 | @IBInspectable var _105ˮ_constant: CGSize {
165 | get { return inch105 ?? CGSize(width: constant, height: constant) }
166 | set { setConstantValue(newValue, for: .inch105) }
167 | }
168 |
169 | @IBInspectable var _111ˮ_constant: CGSize {
170 | get { return inch111 ?? CGSize(width: constant, height: constant) }
171 | set { setConstantValue(newValue, for: .inch111) }
172 | }
173 |
174 | @IBInspectable var _129ˮ_constant: CGSize {
175 | get { return inch129 ?? CGSize(width: constant, height: constant) }
176 | set { setConstantValue(newValue, for: .inch129) }
177 | }
178 | }
179 |
180 |
--------------------------------------------------------------------------------
/Sources/IBMultiplierLayoutConstraint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IBMultiplierLayoutConstraint.swift
3 | // IBLayoutConstraint
4 | //
5 | // Created by Maks Kurpa on 4/3/20.
6 | // Copyright © 2020 Maks Kurpa. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class IBMultiplierLayoutConstraint: IBLayoutConstraint {
12 |
13 | private var isPortrait: Bool?
14 |
15 | @objc override func orientationDidChange(_ note: Notification?) {
16 | super.orientationDidChange(note)
17 | performSelector(onMainThread: #selector(setup), with: nil, waitUntilDone: false)
18 | }
19 |
20 | @objc override func setup() {
21 | super.setup()
22 | if isPortrait == UIDevice.isPortrait {
23 | return
24 | }
25 | isPortrait = UIDevice.isPortrait
26 |
27 | if let multiplierValue = self.multiplierValue(), multiplierValue != self.multiplier {
28 | IBMultiplierLayoutConstraint.changeMultiplier(self, multiplier: multiplierValue)
29 | }
30 | }
31 |
32 | private var inch40_mltplr: CGSize?
33 | private var inch47_mltplr: CGSize?
34 | private var inch55_mltplr: CGSize?
35 | private var inch58_mltplr: CGSize?
36 | private var inch61_mltplr: CGSize?
37 | private var inch65_mltplr: CGSize?
38 |
39 | private var inch79_mltplr: CGSize?
40 | private var inch97_mltplr: CGSize?
41 | private var inch102_mltplr: CGSize?
42 | private var inch105_mltplr: CGSize?
43 | private var inch111_mltplr: CGSize?
44 | private var inch129_mltplr: CGSize?
45 |
46 | internal func multiplierValue() -> CGFloat? {
47 | let values: CGSize?
48 | switch UIDevice.currentScreen {
49 | case .inch40: values = inch40_mltplr
50 | case .inch47: values = inch47_mltplr
51 | case .inch55: values = inch55_mltplr
52 | case .inch58: values = inch58_mltplr
53 | case .inch61: values = inch61_mltplr
54 | case .inch65: values = inch65_mltplr
55 | case .inch79: values = inch79_mltplr
56 | case .inch97: values = inch97_mltplr
57 | case .inch102: values = inch102_mltplr
58 | case .inch105: values = inch105_mltplr
59 | case .inch111: values = inch111_mltplr
60 | case .inch129: values = inch129_mltplr
61 | case .unknown: values = CGSize(width: self.multiplier, height: self.multiplier)
62 | }
63 | return UIDevice.isPortrait ? values?.height : values?.width
64 | }
65 |
66 | private func setMultiplierValue(_ value: CGSize ,for screen: UIDevice.Screen){
67 | switch screen {
68 | case .inch40: inch40_mltplr = value
69 | case .inch47: inch47_mltplr = value
70 | case .inch55: inch55_mltplr = value
71 | case .inch58: inch58_mltplr = value
72 | case .inch61: inch61_mltplr = value
73 | case .inch65: inch65_mltplr = value
74 | case .inch79: inch79_mltplr = value
75 | case .inch97: inch97_mltplr = value
76 | case .inch102: inch102_mltplr = value
77 | case .inch105: inch105_mltplr = value
78 | case .inch111: inch111_mltplr = value
79 | case .inch129: inch129_mltplr = value
80 | case .unknown: break
81 | }
82 | if screen == UIDevice.currentScreen {
83 | let multiplier = UIDevice.isPortrait ? value.height : value.width
84 | self.setValue(multiplier, forKey: "multiplier")
85 | IBMultiplierLayoutConstraint.changeMultiplier(self, multiplier: multiplier)
86 | }
87 | }
88 |
89 | }
90 |
91 | extension IBMultiplierLayoutConstraint {
92 |
93 | @IBInspectable var _40ˮ_multiplayer: CGSize {
94 | get { return inch40_mltplr ?? CGSize(width: multiplier, height: multiplier) }
95 | set { setMultiplierValue(newValue, for: .inch40) }
96 | }
97 |
98 | @IBInspectable var _47ˮ_multiplayer: CGSize {
99 | get { return inch47_mltplr ?? CGSize(width: multiplier, height: multiplier) }
100 | set { setMultiplierValue(newValue, for: .inch47) }
101 | }
102 |
103 | @IBInspectable var _55ˮ_multiplayer: CGSize {
104 | get { return inch55_mltplr ?? CGSize(width: multiplier, height: multiplier) }
105 | set { setMultiplierValue(newValue, for: .inch55) }
106 | }
107 |
108 | @IBInspectable var _58ˮ_multiplayer: CGSize {
109 | get { return inch58_mltplr ?? CGSize(width: multiplier, height: multiplier) }
110 | set { setMultiplierValue(newValue, for: .inch58) }
111 | }
112 |
113 | @IBInspectable var _61ˮ_multiplayer: CGSize {
114 | get { return inch61_mltplr ?? CGSize(width: multiplier, height: multiplier) }
115 | set { setMultiplierValue(newValue, for: .inch61) }
116 | }
117 |
118 | @IBInspectable var _65ˮ_multiplayer: CGSize {
119 | get { return inch65_mltplr ?? CGSize(width: multiplier, height: multiplier) }
120 | set { setMultiplierValue(newValue, for: .inch65) }
121 | }
122 |
123 | @IBInspectable var _79ˮ_multiplayer: CGSize {
124 | get { return inch79_mltplr ?? CGSize(width: multiplier, height: multiplier) }
125 | set { setMultiplierValue(newValue, for: .inch79) }
126 | }
127 |
128 | @IBInspectable var _97ˮ_multiplayer: CGSize {
129 | get { return inch97_mltplr ?? CGSize(width: multiplier, height: multiplier) }
130 | set { setMultiplierValue(newValue, for: .inch97) }
131 | }
132 |
133 | @IBInspectable var _102ˮ_multiplayer: CGSize {
134 | get { return inch102_mltplr ?? CGSize(width: multiplier, height: multiplier) }
135 | set { setMultiplierValue(newValue, for: .inch102) }
136 | }
137 |
138 | @IBInspectable var _105ˮ_multiplayer: CGSize {
139 | get { return inch105_mltplr ?? CGSize(width: multiplier, height: multiplier) }
140 | set { setMultiplierValue(newValue, for: .inch105) }
141 | }
142 |
143 | @IBInspectable var _111ˮ_multiplayer: CGSize {
144 | get { return inch105_mltplr ?? CGSize(width: multiplier, height: multiplier) }
145 | set { setMultiplierValue(newValue, for: .inch105) }
146 | }
147 |
148 | @IBInspectable var _129ˮ_multiplayer: CGSize {
149 | get { return inch129_mltplr ?? CGSize(width: multiplier, height: multiplier) }
150 | set { setMultiplierValue(newValue, for: .inch129) }
151 | }
152 | }
153 |
154 | extension IBMultiplierLayoutConstraint {
155 |
156 | static func changeMultiplier(_ constraint: IBMultiplierLayoutConstraint, multiplier: CGFloat) {
157 | if (String(format:"%.2f", constraint.multiplier) == String(format:"%.2f", multiplier) || multiplier == 0) {
158 | return
159 | }
160 | NSLayoutConstraint.deactivate([constraint])
161 |
162 | let newConstraint = IBMultiplierLayoutConstraint(
163 | item: constraint.firstItem as Any,
164 | attribute: constraint.firstAttribute,
165 | relatedBy: constraint.relation,
166 | toItem: constraint.secondItem,
167 | attribute: constraint.secondAttribute,
168 | multiplier: multiplier,
169 | constant: constraint.constant)
170 |
171 | newConstraint.priority = constraint.priority
172 | newConstraint.shouldBeArchived = true
173 | newConstraint.identifier = constraint.identifier
174 |
175 | newConstraint.inch40 = constraint.inch40
176 | newConstraint.inch47 = constraint.inch47
177 | newConstraint.inch55 = constraint.inch55
178 | newConstraint.inch58 = constraint.inch58
179 | newConstraint.inch61 = constraint.inch61
180 | newConstraint.inch65 = constraint.inch65
181 |
182 | newConstraint.inch79 = constraint.inch79
183 | newConstraint.inch97 = constraint.inch97
184 | newConstraint.inch47 = constraint.inch47
185 | newConstraint.inch105 = constraint.inch105
186 | newConstraint.inch111 = constraint.inch111
187 | newConstraint.inch129 = constraint.inch129
188 |
189 | newConstraint.inch40_mltplr = constraint.inch40_mltplr
190 | newConstraint.inch47_mltplr = constraint.inch47_mltplr
191 | newConstraint.inch55_mltplr = constraint.inch55_mltplr
192 | newConstraint.inch58_mltplr = constraint.inch58_mltplr
193 | newConstraint.inch61_mltplr = constraint.inch61_mltplr
194 | newConstraint.inch65_mltplr = constraint.inch65_mltplr
195 |
196 | newConstraint.inch79_mltplr = constraint.inch79_mltplr
197 | newConstraint.inch97_mltplr = constraint.inch97_mltplr
198 | newConstraint.inch47_mltplr = constraint.inch47_mltplr
199 | newConstraint.inch105_mltplr = constraint.inch105_mltplr
200 | newConstraint.inch111_mltplr = constraint.inch111_mltplr
201 | newConstraint.inch129_mltplr = constraint.inch129_mltplr
202 |
203 | NSLayoutConstraint.activate([newConstraint])
204 | newConstraint.setupObserver()
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/Sources/UIDevice+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIDevice+Extension.swift
3 | // IBLayoutConstraint
4 | //
5 | // Created by Maks Kurpa on 4/3/20.
6 | // Copyright © 2020 Maks Kurpa. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIDevice {
12 |
13 | public enum Screen {
14 | case inch40
15 | case inch47
16 | case inch55
17 | case inch58
18 | case inch61
19 | case inch65
20 |
21 | case inch79
22 | case inch97
23 | case inch102
24 | case inch105
25 | case inch111
26 | case inch129
27 |
28 | case unknown
29 | }
30 |
31 | public enum Scale {
32 | case x1
33 | case x2
34 | case x3
35 | case unknown
36 | }
37 |
38 | public static var isIpad: Bool = {
39 | return UIDevice.current.userInterfaceIdiom == .pad
40 | }()
41 |
42 | public static var isPortrait: Bool {
43 | return UIApplication.shared.statusBarOrientation.isPortrait
44 | }
45 |
46 | public static var currentScreen: UIDevice.Screen =
47 | {
48 | let maxSide = max(UIScreen.main.bounds.height, UIScreen.main.bounds.width)
49 | let mimSide = min(UIScreen.main.bounds.height, UIScreen.main.bounds.width)
50 |
51 | if maxSide == 568 { return .inch40 }
52 | else if maxSide == 667 { return .inch47 }
53 | else if maxSide == 736 { return .inch55 }
54 | else if maxSide == 812 { return .inch58 }
55 | else if maxSide == 896 && scale == .x2 { return .inch61 }
56 | else if maxSide == 896 && scale == .x3 { return .inch65 }
57 |
58 | else if maxSide == 1024, UIScreen.pointsPerCentimeter == 326 { return .inch79 }
59 | else if maxSide == 1024 { return .inch97 }
60 | else if maxSide == 1080 { return .inch105 }
61 | else if maxSide == 1112 { return .inch105 }
62 | else if maxSide == 1194 { return .inch111 }
63 | else if maxSide == 1366 { return .inch129 }
64 | else if maxSide == 960 { return .inch97}
65 | else { return .unknown }
66 | }()
67 |
68 | public static var scale: UIDevice.Scale = {
69 | let scale = Int(UIScreen.main.scale)
70 | if scale == 1 { return .x1 }
71 | else if scale == 2 { return .x2 }
72 | else if scale == 3 { return .x3 }
73 | else { return .unknown }
74 | }()
75 |
76 | static let modelIdentifier: String = {
77 | if let simulatorModelIdentifier = ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] { return simulatorModelIdentifier }
78 | var sysinfo = utsname()
79 | uname(&sysinfo) // ignore return value
80 | return String(bytes: Data(bytes: &sysinfo.machine, count: Int(_SYS_NAMELEN)), encoding: .ascii)!.trimmingCharacters(in: .controlCharacters)
81 | }()
82 | }
83 |
--------------------------------------------------------------------------------
/Sources/UIScreen+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIScreen+Extension.swift
3 | // IBLayoutConstraint
4 | //
5 | // Created by Maks Kurpa on 4/3/20.
6 | // Copyright © 2020 Maks Kurpa. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | private func computeIfSome(optional: T?, computation: ((T) -> S)) -> S? { if let some = optional { return computation(some) } else { return .none } }
12 |
13 | public extension UIScreen {
14 |
15 | /// The number of pixels per inch for this device
16 | static let pixelsPerInch: CGFloat? = {
17 | switch UIDevice.modelIdentifier {
18 | case "iPad2,1", "iPad2,2", "iPad2,3", "iPad2,4": // iPad 2
19 | return 132
20 |
21 | case "iPad2,5", "iPad2,6", "iPad2,7": // iPad Mini
22 | return 163
23 |
24 | case "iPad3,1", "iPad3,2", "iPad3,3": fallthrough // iPad 3rd generation
25 | case "iPad3,4", "iPad3,5", "iPad3,6": fallthrough // iPad 4th generation
26 | case "iPad4,1", "iPad4,2", "iPad4,3": fallthrough // iPad Air
27 | case "iPad5,3", "iPad5,4": fallthrough // iPad Air 2
28 | case "iPad6,7", "iPad6,8": fallthrough // iPad Pro (12.9 inch)
29 | case "iPad6,3", "iPad6,4": fallthrough // iPad Pro (9.7 inch)
30 | case "iPad6,11", "iPad6,12": fallthrough // iPad 5th generation
31 | case "iPad7,1", "iPad7,2": fallthrough // iPad Pro (12.9 inch, 2nd generation)
32 | case "iPad7,3", "iPad7,4": fallthrough // iPad Pro (10.5 inch)
33 | case "iPad7,5", "iPad7,6": fallthrough // iPad 6th generation
34 | case "iPad8,1", "iPad8,2", "iPad8,3", "iPad8,4": fallthrough // iPad Pro (11 inch)
35 | case "iPad8,5", "iPad8,6", "iPad8,7", "iPad8,8": // iPad Pro (12.9 inch, 3rd generation)
36 | return 264
37 |
38 | case "iPhone4,1": fallthrough // iPhone 4S
39 | case "iPhone5,1", "iPhone5,2": fallthrough // iPhone 5
40 | case "iPhone5,3", "iPhone5,4": fallthrough // iPhone 5C
41 | case "iPhone6,1", "iPhone6,2": fallthrough // iPhone 5S
42 | case "iPhone8,4": fallthrough // iPhone SE
43 | case "iPhone7,2": fallthrough // iPhone 6
44 | case "iPhone8,1": fallthrough // iPhone 6S
45 | case "iPhone9,1", "iPhone9,3": fallthrough // iPhone 7
46 | case "iPhone10,1", "iPhone10,4": fallthrough // iPhone 8
47 | case "iPhone11,8": fallthrough // iPhone XR
48 | case "iPod5,1": fallthrough // iPod Touch 5th generation
49 | case "iPod7,1": fallthrough // iPod Touch 6th generation
50 | case "iPad4,4", "iPad4,5", "iPad4,6": fallthrough // iPad Mini 2
51 | case "iPad4,7", "iPad4,8", "iPad4,9": fallthrough // iPad Mini 3
52 | case "iPad5,1", "iPad5,2": // iPad Mini 4
53 | return 326
54 |
55 | case "iPhone7,1": fallthrough // iPhone 6 Plus
56 | case "iPhone8,2": fallthrough // iPhone 6S Plus
57 | case "iPhone9,2", "iPhone9,4": fallthrough // iPhone 7 Plus
58 | case "iPhone10,2", "iPhone10,5": // iPhone 8 Plus
59 | return 401
60 |
61 | case "iPhone10,3", "iPhone10,6": fallthrough // iPhone X
62 | case "iPhone11,2": fallthrough // iPhone XS
63 | case "iPhone11,4", "iPhone11,6": // iPhone XS Max
64 | return 458
65 |
66 | default: // unknown model identifier
67 | return .none
68 | }
69 | }()
70 |
71 | /// The number of pixels per centimeter for this device
72 | static let pixelsPerCentimeter: CGFloat? = computeIfSome(optional: pixelsPerInch, computation: { $0 / 2.54 })
73 |
74 | /// The number of points per inch for this device
75 | static let pointsPerInch: CGFloat? = computeIfSome(optional: pixelsPerInch, computation: { $0 / main.nativeScale })
76 |
77 | /// The number of points per centimeter for this device
78 | static let pointsPerCentimeter: CGFloat? = computeIfSome(optional: pixelsPerCentimeter, computation: { $0 / main.nativeScale })
79 |
80 | /// The screen dimension in inches
81 | static let dimensionInInches: CGSize? = computeIfSome(optional: pixelsPerInch, computation: { CGSize(width: main.nativeBounds.width / $0, height: main.nativeBounds.height / $0) })
82 |
83 | /// The screen dimension in centimeters
84 | static let dimensionInCentimeters: CGSize? = computeIfSome(optional: pixelsPerCentimeter, computation: { CGSize(width: main.nativeBounds.width / $0, height: main.nativeBounds.height / $0) })
85 |
86 | }
87 |
88 |
--------------------------------------------------------------------------------
/Sources/UIView+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIView+Extension.swift
3 | // IBLayoutConstraint
4 | //
5 | // Created by Maks Kurpa on 4/3/20.
6 | // Copyright © 2020 Maks Kurpa. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIView {
12 | static var safeAreaIndents: UIEdgeInsets {
13 | if #available(iOS 11.0, *) {
14 | return UIApplication.shared.delegate?.window??.safeAreaInsets ?? .zero
15 | }
16 | return .zero
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/docs/ib_screen_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaksimKurpa/IBLayoutConstraint/af3471701b2dcbe4d3fa85f60c6411a9e28d7c70/docs/ib_screen_1.png
--------------------------------------------------------------------------------
/docs/ib_screen_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaksimKurpa/IBLayoutConstraint/af3471701b2dcbe4d3fa85f60c6411a9e28d7c70/docs/ib_screen_2.png
--------------------------------------------------------------------------------