├── .github
└── dependabot.yml
├── .gitignore
├── LICENSE
├── NOTICE
├── README.md
├── Sample
├── AppDelegate.cs
├── Entitlements.plist
├── Info.plist
├── Main.cs
├── Properties
│ └── AssemblyInfo.cs
├── Resources
│ └── LaunchScreen.xib
├── Sample.csproj
└── ViewController.cs
├── TZStackView.nuspec
├── TZStackView.sln
├── TZStackView
├── Alignment.cs
├── AnimationDelegate.cs
├── Distribution.cs
├── Properties
│ └── AssemblyInfo.cs
├── SpacerView.cs
├── StackView.cs
└── TZStackView.csproj
├── appveyor.yml
├── assets
└── layout-example.png
├── build.cake
├── build.ps1
├── build.sh
├── releasenotes.md
└── tools
└── packages.config
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: nuget
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | time: "04:00"
8 | open-pull-requests-limit: 10
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | ## Android Auto-generated files
5 | Resource.designer.cs
6 |
7 | # User-specific files
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Build results
17 | [Dd]ebug/
18 | [Dd]ebugPublic/
19 | [Rr]elease/
20 | [Rr]eleases/
21 | x64/
22 | x86/
23 | build/
24 | bld/
25 | [Bb]in/
26 | [Oo]bj/
27 |
28 | # Visual Studo 2015 cache/options directory
29 | .vs/
30 |
31 | # MSTest test Results
32 | [Tt]est[Rr]esult*/
33 | [Tt]est[Rr]esult*
34 | [Bb]uild[Ll]og.*
35 |
36 | # NUNIT
37 | *.VisualState.xml
38 | TestResult.xml
39 |
40 | # Build Results of an ATL Project
41 | [Dd]ebugPS/
42 | [Rr]eleasePS/
43 | dlldata.c
44 |
45 | *_i.c
46 | *_p.c
47 | *_i.h
48 | *.ilk
49 | *.meta
50 | *.obj
51 | *.pch
52 | *.pdb
53 | *.pgc
54 | *.pgd
55 | *.rsp
56 | *.sbr
57 | *.tlb
58 | *.tli
59 | *.tlh
60 | *.tmp
61 | *.tmp_proj
62 | *.log
63 | *.vspscc
64 | *.vssscc
65 | .builds
66 | *.pidb
67 | *.svclog
68 | *.scc
69 | *.exe
70 | *.bak
71 | *.cache
72 | *.lib
73 |
74 | # Chutzpah Test files
75 | _Chutzpah*
76 |
77 | # Visual C++ cache files
78 | ipch/
79 | *.aps
80 | *.ncb
81 | *.opensdf
82 | *.sdf
83 | *.cachefile
84 |
85 | # Visual Studio profiler
86 | *.psess
87 | *.vsp
88 | *.vspx
89 |
90 | # TFS 2012 Local Workspace
91 | $tf/
92 |
93 | # Guidance Automation Toolkit
94 | *.gpState
95 |
96 | # ReSharper is a .NET coding add-in
97 | _ReSharper*/
98 | *.[Rr]e[Ss]harper
99 | *.DotSettings.user
100 |
101 | # JustCode is a .NET coding addin-in
102 | .JustCode
103 |
104 | # TeamCity is a build add-in
105 | _TeamCity*
106 |
107 | # DotCover is a Code Coverage Tool
108 | *.dotCover
109 |
110 | # NCrunch
111 | _NCrunch_*
112 | .*crunch*.local.xml
113 | *.ncrunchproject
114 |
115 | # MightyMoose
116 | *.mm.*
117 | AutoTest.Net/
118 |
119 | # Web workbench (sass)
120 | .sass-cache/
121 |
122 | # Installshield output folder
123 | [Ee]xpress/
124 |
125 | # DocProject is a documentation generator add-in
126 | DocProject/buildhelp/
127 | DocProject/Help/*.HxT
128 | DocProject/Help/*.HxC
129 | DocProject/Help/*.hhc
130 | DocProject/Help/*.hhk
131 | DocProject/Help/*.hhp
132 | DocProject/Help/Html2
133 | DocProject/Help/html
134 |
135 | # Click-Once directory
136 | publish/
137 |
138 | # Publish Web Output
139 | *.[Pp]ublish.xml
140 | *.azurePubxml
141 | # TODO: Comment the next line if you want to checkin your web deploy settings
142 | # but database connection strings (with potential passwords) will be unencrypted
143 | *.pubxml
144 | *.publishproj
145 |
146 | # NuGet Packages
147 | *.nupkg
148 | # The packages folder can be ignored because of Package Restore
149 | **/packages/*
150 | # except build/, which is used as an MSBuild target.
151 | !**/packages/build/
152 | # Uncomment if necessary however generally it will be regenerated when needed
153 | #!**/packages/repositories.config
154 | packages/
155 | !packages/Microsoft.Office.js.1.0.0/
156 | !packages/repositories.config
157 |
158 | #Allow NuGet.exe (do not ignore)
159 | !NuGet.exe
160 | *.orig
161 |
162 | # Windows Azure Build Output
163 | csx/
164 | *.build.csdef
165 |
166 | # Windows Store app package directory
167 | AppPackages/
168 |
169 | # Others
170 | *.[Cc]ache
171 | ClientBin/
172 | [Ss]tyle[Cc]op.*
173 | ~$*
174 | *~
175 | *.dbmdl
176 | *.dbproj.schemaview
177 | *.pfx
178 | *.publishsettings
179 | node_modules/
180 | bower_components/
181 |
182 | # RIA/Silverlight projects
183 | Generated_Code/
184 |
185 | # Backup & report files from converting an old project file
186 | # to a newer Visual Studio version. Backup files are not needed,
187 | # because we have git ;-)
188 | _UpgradeReport_Files/
189 | Backup*/
190 | UpgradeLog*.XML
191 | UpgradeLog*.htm
192 |
193 | # SQL Server files
194 | *.mdf
195 | *.ldf
196 |
197 | # Business Intelligence projects
198 | *.rdl.data
199 | *.bim.layout
200 | *.bim_*.settings
201 |
202 | # Microsoft Fakes
203 | FakesAssemblies/
204 |
205 | # Node.js Tools for Visual Studio
206 | .ntvs_analysis.dat
207 |
208 | # Visual Studio 6 build log
209 | *.plg
210 |
211 | # Visual Studio 6 workspace options file
212 | *.opt
213 |
214 | #ignore thumbnails created by windows
215 | Thumbs.db
216 |
217 | # Xcode
218 | .DS_Store
219 | */build/*
220 | *.pbxuser
221 | !default.pbxuser
222 | *.mode1v3
223 | !default.mode1v3
224 | *.mode2v3
225 | !default.mode2v3
226 | *.perspectivev3
227 | !default.perspectivev3
228 | xcuserdata
229 | profile
230 | *.moved-aside
231 | DerivedData
232 | .idea/
233 | *.hmap
234 | *.xccheckout
235 |
236 | #CocoaPods
237 | Pods
238 | nuspec/nuget.exe
239 |
240 | #Build
241 | project.lock.json
242 | tools/Cake/
243 | tools/Addins/
244 | tools/gitlink/
245 | tools/GitVersion.CommandLine/
246 | tools/nuget.exe
247 | tools/packages.config.md5sum
248 | artifacts/
249 | *.nupkg
250 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Tom van Zummeren
4 | Copyright (c) 2016 Tomasz Cielecki
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | The code in this project was ported from Swift to Xamarin.iOS by Tomasz Cielecki (tomasz@ostebaronen.dk)
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TZStackView
2 |
3 | [](https://ci.appveyor.com/project/Cheesebaron/tzstackview)
4 |
5 | This is a port of [Tom van Zummeren's][tvz] [TZStackView][tzstackview], which provides a backport of UIStackView, which was introduced in iOS 9.
6 |
7 | StackView provides an easy way to lay out views horizontally and vertically and UIStackView is the new recommended way to lay out things in iOS 9,
8 | instead of resolving to use explicit view constraints.
9 |
10 | ## Features
11 | - Compatible with iOS 7 and 8
12 | - Complete API of `UIStackView` with all distribution and alignment options
13 | - Animation of hidden property
14 |
15 | - Support for storyboards.
16 |
17 | ## Installing
18 | Add the TZStackView nuget to your Xamarin.iOS project
19 |
20 | > Install-Package TZStackView
21 |
22 | ## Usage
23 | Given `view1`, `view2` and `view3` have intrinsic content sizes set to 100x100, 80x80 and 60x60 respectively.
24 |
25 | ```
26 | var stackView = new StackView(new UIView[] {view1, view2, view3})
27 | {
28 | Axis = UILayoutConstraintAxis.Vertical,
29 | Distribution = Distribution.FillEqually,
30 | Alignment = Alignment.Center,
31 | Spacing = 25
32 | };
33 | ```
34 |
35 | This will produce the following layout.
36 | ![Layout Example][layout]
37 |
38 | To animate adding or removing a view from the arranged subviews simply toggle the `Hidden` property on the view.
39 |
40 | ```
41 | UIView.AnimateNotify(0.6, 0, 0.7f, 0, UIViewAnimationOptions.AllowUserInteraction,
42 | () => { view.Hidden = true; }, completed => { });
43 | ```
44 |
45 | ## Migrating to UIStackView
46 | If you at some point want to make iOS 9 the minimum target of your application, you will want to replace this with `UIStackView`.
47 | Since `TZStackView` is a drop in replacement of `UIStackView`, you should be able to just use `UIStackView` instead.
48 |
49 | ```
50 | var stackView = StackView(subViews);
51 | ```
52 |
53 | ```
54 | var stackView = UIStackView(subViews);
55 | ```
56 |
57 | ## License
58 | TZStackView is licensed under the MIT License. See the [LICENSE](/LICENSE) file for details.
59 |
60 | [tvz]: https://github.com/tomvanzummeren
61 | [tzstackview]: https://github.com/tomvanzummeren/TZStackView
62 | [layout]: /assets/layout-example.png
63 |
--------------------------------------------------------------------------------
/Sample/AppDelegate.cs:
--------------------------------------------------------------------------------
1 | using Foundation;
2 | using UIKit;
3 |
4 | namespace Sample
5 | {
6 | // The UIApplicationDelegate for the application. This class is responsible for launching the
7 | // User Interface of the application, as well as listening (and optionally responding) to application events from iOS.
8 | [Register("AppDelegate")]
9 | public class AppDelegate : UIApplicationDelegate
10 | {
11 | // class-level declarations
12 |
13 | public override UIWindow Window
14 | {
15 | get;
16 | set;
17 | }
18 |
19 | public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
20 | {
21 | // create a new window instance based on the screen size
22 | Window = new UIWindow(UIScreen.MainScreen.Bounds);
23 |
24 | UISegmentedControl.Appearance.SetTitleTextAttributes (new UITextAttributes () {
25 | Font = UIFont.FromName ("HelveticaNeue-Light", 10f)
26 | }, UIControlState.Normal);
27 |
28 | Window.RootViewController = new ViewController ();
29 | Window.MakeKeyAndVisible();
30 | return true;
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Sample/Entitlements.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/Sample/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDisplayName
6 | Sample
7 | CFBundleIdentifier
8 | com.companyname.Sample
9 | CFBundleShortVersionString
10 | 1.0
11 | CFBundleVersion
12 | 1.0
13 | LSRequiresIPhoneOS
14 |
15 | MinimumOSVersion
16 | 9.2
17 | UIDeviceFamily
18 |
19 | 1
20 | 2
21 |
22 | UILaunchStoryboardName
23 | LaunchScreen
24 | UIRequiredDeviceCapabilities
25 |
26 | armv7
27 |
28 | UISupportedInterfaceOrientations
29 |
30 | UIInterfaceOrientationPortrait
31 | UIInterfaceOrientationLandscapeLeft
32 | UIInterfaceOrientationLandscapeRight
33 |
34 | UISupportedInterfaceOrientations~ipad
35 |
36 | UIInterfaceOrientationPortrait
37 | UIInterfaceOrientationPortraitUpsideDown
38 | UIInterfaceOrientationLandscapeLeft
39 | UIInterfaceOrientationLandscapeRight
40 |
41 | UIMainNibFile
42 | LaunchScreen
43 |
44 |
45 |
--------------------------------------------------------------------------------
/Sample/Main.cs:
--------------------------------------------------------------------------------
1 | using UIKit;
2 |
3 | namespace Sample
4 | {
5 | public class Application
6 | {
7 | // This is the main entry point of the application.
8 | static void Main(string[] args)
9 | {
10 | // if you want to use a different Application Delegate class from "AppDelegate"
11 | // you can specify it here.
12 | UIApplication.Main(args, null, "AppDelegate");
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/Sample/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("Sample")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("Sample")]
13 | [assembly: AssemblyCopyright("Copyright © 2016")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("150d8a38-4419-4330-a42b-12879acb7a54")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/Sample/Resources/LaunchScreen.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
21 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/Sample/Sample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | iPhoneSimulator
6 | {150D8A38-4419-4330-A42B-12879ACB7A54}
7 | {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
8 | Exe
9 | Sample
10 | Resources
11 | Sample
12 |
13 |
14 | true
15 | full
16 | false
17 | bin\iPhoneSimulator\Debug
18 | DEBUG
19 | prompt
20 | 4
21 | false
22 | i386
23 | None
24 | true
25 |
26 |
27 | none
28 | true
29 | bin\iPhoneSimulator\Release
30 | prompt
31 | 4
32 | None
33 | i386
34 | false
35 |
36 |
37 | true
38 | full
39 | false
40 | bin\iPhone\Debug
41 | DEBUG
42 | prompt
43 | 4
44 | false
45 | ARMv7, ARM64
46 | Entitlements.plist
47 | iPhone Developer
48 | true
49 |
50 |
51 | none
52 | true
53 | bin\iPhone\Release
54 | prompt
55 | 4
56 | Entitlements.plist
57 | ARMv7, ARM64
58 | false
59 | iPhone Developer
60 |
61 |
62 | none
63 | True
64 | bin\iPhone\Ad-Hoc
65 | prompt
66 | 4
67 | False
68 | ARMv7, ARM64
69 | Entitlements.plist
70 | True
71 | Automatic:AdHoc
72 | iPhone Distribution
73 |
74 |
75 | none
76 | True
77 | bin\iPhone\AppStore
78 | prompt
79 | 4
80 | False
81 | ARMv7, ARM64
82 | Entitlements.plist
83 | Automatic:AppStore
84 | iPhone Distribution
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}
104 | TZStackView
105 | False
106 | False
107 |
108 |
109 |
110 |
--------------------------------------------------------------------------------
/Sample/ViewController.cs:
--------------------------------------------------------------------------------
1 | using CoreGraphics;
2 | using Foundation;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Text;
6 | using TZStackView;
7 | using UIKit;
8 |
9 | namespace Sample
10 | {
11 | public class ViewController : UIViewController
12 | {
13 | private StackView _stackView;
14 |
15 | private UIButton _resetButton = new UIButton(UIButtonType.System);
16 | private UILabel _instructionLabel = new UILabel();
17 |
18 | private UISegmentedControl _axisSegmentedControl;
19 | private UISegmentedControl _alignmentSegmentedControl;
20 | private UISegmentedControl _distributionSegmentedControl;
21 |
22 | public override void ViewDidLoad()
23 | {
24 | base.ViewDidLoad();
25 | EdgesForExtendedLayout = UIRectEdge.None;
26 |
27 | View.BackgroundColor = UIColor.Black;
28 | Title = "TZStackView";
29 |
30 | _stackView = new StackView(CreateViews())
31 | {
32 | TranslatesAutoresizingMaskIntoConstraints = false,
33 | Axis = UILayoutConstraintAxis.Vertical,
34 | Distribution = Distribution.Fill,
35 | Alignment = Alignment.Fill,
36 | Spacing = 15
37 | };
38 | Add(_stackView);
39 |
40 | _instructionLabel.TranslatesAutoresizingMaskIntoConstraints = false;
41 | _instructionLabel.Font = UIFont.SystemFontOfSize(15);
42 | _instructionLabel.Text = "Tap any of the boxes to set Hidden=true";
43 | _instructionLabel.TextColor = UIColor.White;
44 | _instructionLabel.Lines = 0;
45 | _instructionLabel.SetContentCompressionResistancePriority(900, UILayoutConstraintAxis.Horizontal);
46 | _instructionLabel.SetContentHuggingPriority(1000, UILayoutConstraintAxis.Vertical);
47 | Add(_instructionLabel);
48 |
49 | _resetButton.TranslatesAutoresizingMaskIntoConstraints = false;
50 | _resetButton.SetTitle("Reset", UIControlState.Normal);
51 | _resetButton.TouchUpInside += (_, __) => Reset();
52 | _resetButton.SetContentCompressionResistancePriority(1000, UILayoutConstraintAxis.Horizontal);
53 | _resetButton.SetContentHuggingPriority(1000, UILayoutConstraintAxis.Horizontal);
54 | _resetButton.SetContentHuggingPriority(1000, UILayoutConstraintAxis.Vertical);
55 | Add(_resetButton);
56 |
57 | _axisSegmentedControl = new UISegmentedControl ();
58 | _axisSegmentedControl.InsertSegment ("Vertical", 0, false);
59 | _axisSegmentedControl.InsertSegment ("Horizontal", 1, false);
60 | _axisSegmentedControl.SelectedSegment = 0;
61 | _axisSegmentedControl.ValueChanged += (s, __) => AxisChanged(s as UISegmentedControl);
62 | _axisSegmentedControl.SetContentCompressionResistancePriority(1000, UILayoutConstraintAxis.Horizontal);
63 | _axisSegmentedControl.TintColor = UIColor.LightGray;
64 |
65 | _alignmentSegmentedControl = new UISegmentedControl ();
66 | _alignmentSegmentedControl.InsertSegment ("Fill", 0, false);
67 | _alignmentSegmentedControl.InsertSegment ("Center", 1, false);
68 | _alignmentSegmentedControl.InsertSegment ("Leading", 2, false);
69 | _alignmentSegmentedControl.InsertSegment ("Top", 3, false);
70 | _alignmentSegmentedControl.InsertSegment ("Trailing", 4, false);
71 | _alignmentSegmentedControl.InsertSegment ("Bottom", 5, false);
72 | _alignmentSegmentedControl.InsertSegment ("FirstBaseline", 6, false);
73 | _alignmentSegmentedControl.SelectedSegment = 0;
74 | _alignmentSegmentedControl.ValueChanged += (s, __) => AlignmentChanged(s as UISegmentedControl);
75 | _alignmentSegmentedControl.SetContentCompressionResistancePriority(1000, UILayoutConstraintAxis.Horizontal);
76 | _alignmentSegmentedControl.TintColor = UIColor.LightGray;
77 |
78 | _distributionSegmentedControl = new UISegmentedControl ();
79 | _distributionSegmentedControl.InsertSegment ("Fill", 0, false);
80 | _distributionSegmentedControl.InsertSegment ("FillEqually", 1, false);
81 | _distributionSegmentedControl.InsertSegment ("FillProportionally", 2, false);
82 | _distributionSegmentedControl.InsertSegment ("EqualSpacing", 3, false);
83 | _distributionSegmentedControl.InsertSegment ("EqualCentering", 4, false);
84 | _distributionSegmentedControl.SelectedSegment = 0;
85 | _distributionSegmentedControl.ValueChanged += (s, __) => DistributionChanged(s as UISegmentedControl);
86 | _distributionSegmentedControl.TintColor = UIColor.LightGray;
87 |
88 | var controlsLayoutContainer = new StackView(new UIView[]
89 | {
90 | _axisSegmentedControl, _alignmentSegmentedControl, _distributionSegmentedControl
91 | })
92 | {
93 | Axis = UILayoutConstraintAxis.Vertical,
94 | TranslatesAutoresizingMaskIntoConstraints = false,
95 | Spacing = 5
96 | };
97 | controlsLayoutContainer.SetContentHuggingPriority(1000, UILayoutConstraintAxis.Vertical);
98 | Add(controlsLayoutContainer);
99 |
100 | var views = new NSMutableDictionary
101 | {
102 | {new NSString("instructionLabel"), _instructionLabel },
103 | {new NSString("resetButton"), _resetButton },
104 | {new NSString("stackView"), _stackView },
105 | {new NSString("controlsLayoutContainer"), controlsLayoutContainer }
106 | };
107 |
108 | var metrics = new NSMutableDictionary
109 | {
110 | {new NSString("gap"), FromObject(10) },
111 | {new NSString("topspacing"), FromObject(25) }
112 | };
113 |
114 | View.AddConstraints (NSLayoutConstraint.FromVisualFormat ("H:|-gap-[instructionLabel]-[resetButton]-gap-|",
115 | NSLayoutFormatOptions.DirectionLeadingToTrailing, metrics, views));
116 | View.AddConstraints (NSLayoutConstraint.FromVisualFormat ("H:|[stackView]|",
117 | NSLayoutFormatOptions.DirectionLeadingToTrailing, metrics, views));
118 | View.AddConstraints (NSLayoutConstraint.FromVisualFormat ("H:|[controlsLayoutContainer]|",
119 | NSLayoutFormatOptions.DirectionLeadingToTrailing, metrics, views));
120 |
121 | View.AddConstraints (NSLayoutConstraint.FromVisualFormat (
122 | "V:|-topspacing-[instructionLabel]-gap-[controlsLayoutContainer]-gap-[stackView]|",
123 | NSLayoutFormatOptions.DirectionLeadingToTrailing, metrics, views));
124 | View.AddConstraints (NSLayoutConstraint.FromVisualFormat (
125 | "V:|-topspacing-[resetButton]-gap-[controlsLayoutContainer]",
126 | NSLayoutFormatOptions.DirectionLeadingToTrailing, metrics, views));
127 |
128 | View.SetNeedsLayout ();
129 | }
130 |
131 | private IEnumerable CreateViews()
132 | {
133 | var redView = new ExplicitIntrinsicContentSizeView(new CGSize(100, 100), "Red");
134 | var greenView = new ExplicitIntrinsicContentSizeView(new CGSize(80, 80), "Green");
135 | var blueView = new ExplicitIntrinsicContentSizeView(new CGSize(60, 60), "Blue");
136 | var purpleView = new ExplicitIntrinsicContentSizeView(new CGSize(80, 80), "Purple");
137 | var yellowView = new ExplicitIntrinsicContentSizeView(new CGSize(100, 100), "Yellow");
138 |
139 | redView.BackgroundColor = UIColor.Red.ColorWithAlpha(0.75f);
140 | greenView.BackgroundColor = UIColor.Green.ColorWithAlpha(0.75f);
141 | blueView.BackgroundColor = UIColor.Blue.ColorWithAlpha(0.75f);
142 | purpleView.BackgroundColor = UIColor.Purple.ColorWithAlpha(0.75f);
143 | yellowView.BackgroundColor = UIColor.Yellow.ColorWithAlpha(0.75f);
144 |
145 | return new[] { redView, greenView, blueView, purpleView, yellowView };
146 | }
147 |
148 | private void Reset()
149 | {
150 | UIView.AnimateNotify(0.6, 0, 0.7f, 0, UIViewAnimationOptions.AllowUserInteraction,
151 | () => {
152 | foreach (var view in _stackView.ArrangedSubviews)
153 | view.Hidden = false;
154 | },
155 | completed => { });
156 | }
157 |
158 | private void AxisChanged(UISegmentedControl sender)
159 | {
160 | if (sender == null) return;
161 |
162 | _stackView.Axis = sender.SelectedSegment == 0 ?
163 | UILayoutConstraintAxis.Vertical : UILayoutConstraintAxis.Horizontal;
164 | }
165 |
166 | private void AlignmentChanged(UISegmentedControl sender)
167 | {
168 | if (sender == null) return;
169 | var index = sender.SelectedSegment;
170 |
171 | switch(index) {
172 | case 0:
173 | _stackView.Alignment = Alignment.Fill;
174 | break;
175 | case 1:
176 | _stackView.Alignment = Alignment.Center;
177 | break;
178 | case 2:
179 | _stackView.Alignment = Alignment.Leading;
180 | break;
181 | case 3:
182 | _stackView.Alignment = Alignment.Top;
183 | break;
184 | case 4:
185 | _stackView.Alignment = Alignment.Trailing;
186 | break;
187 | case 5:
188 | _stackView.Alignment = Alignment.Bottom;
189 | break;
190 | default:
191 | _stackView.Alignment = Alignment.FirstBaseline;
192 | break;
193 | }
194 | }
195 |
196 | private void DistributionChanged(UISegmentedControl sender)
197 | {
198 | if (sender == null) return;
199 | var index = sender.SelectedSegment;
200 |
201 | switch (index) {
202 | case 0:
203 | _stackView.Distribution = Distribution.Fill;
204 | break;
205 | case 1:
206 | _stackView.Distribution = Distribution.FillEqualy;
207 | break;
208 | case 2:
209 | _stackView.Distribution = Distribution.FillProportionally;
210 | break;
211 | case 3:
212 | _stackView.Distribution = Distribution.EqualSpacing;
213 | break;
214 | default:
215 | _stackView.Distribution = Distribution.EqualCentering;
216 | break;
217 | }
218 | }
219 |
220 | public override UIStatusBarStyle PreferredStatusBarStyle() {
221 | return UIStatusBarStyle.LightContent;
222 | }
223 | }
224 |
225 | public class ExplicitIntrinsicContentSizeView : UIView
226 | {
227 | private string _name;
228 | private CGSize _contentSize;
229 |
230 | public ExplicitIntrinsicContentSizeView(CGSize intrinsicContentSize, string name) : base(CGRect.Empty)
231 | {
232 | _name = name;
233 | _contentSize = intrinsicContentSize;
234 |
235 | var gestureRecognizer = new UITapGestureRecognizer(Tap);
236 | AddGestureRecognizer(gestureRecognizer);
237 | UserInteractionEnabled = true;
238 | }
239 |
240 | private void Tap()
241 | {
242 | UIView.AnimateNotify(0.6, 0, 0.7f, 0, UIViewAnimationOptions.AllowUserInteraction,
243 | () => {
244 | Hidden = true;
245 | },
246 | completed => { });
247 | }
248 |
249 | public override CGSize IntrinsicContentSize
250 | {
251 | get { return _contentSize; }
252 | }
253 |
254 | public override string Description
255 | {
256 | get { return _name; }
257 | }
258 | }
259 | }
260 |
--------------------------------------------------------------------------------
/TZStackView.nuspec:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | TZStackView
5 | UIStackView backport for iOS 7 and 8 for Xamarin.iOS
6 |
7 |
--------------------------------------------------------------------------------
/TZStackView.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 14
4 | VisualStudioVersion = 14.0.24720.0
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TZStackView", "TZStackView\TZStackView.csproj", "{EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample", "Sample\Sample.csproj", "{150D8A38-4419-4330-A42B-12879ACB7A54}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{EE34CC8E-4EF8-4C74-922A-4E6B1113F70D}"
11 | ProjectSection(SolutionItems) = preProject
12 | README.md = README.md
13 | TZStackView.nuspec = TZStackView.nuspec
14 | build.cake = build.cake
15 | EndProjectSection
16 | EndProject
17 | Global
18 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
19 | Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
20 | Ad-Hoc|iPhone = Ad-Hoc|iPhone
21 | Ad-Hoc|iPhoneSimulator = Ad-Hoc|iPhoneSimulator
22 | AppStore|Any CPU = AppStore|Any CPU
23 | AppStore|iPhone = AppStore|iPhone
24 | AppStore|iPhoneSimulator = AppStore|iPhoneSimulator
25 | Debug|Any CPU = Debug|Any CPU
26 | Debug|iPhone = Debug|iPhone
27 | Debug|iPhoneSimulator = Debug|iPhoneSimulator
28 | Release|Any CPU = Release|Any CPU
29 | Release|iPhone = Release|iPhone
30 | Release|iPhoneSimulator = Release|iPhoneSimulator
31 | EndGlobalSection
32 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
33 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.Ad-Hoc|Any CPU.ActiveCfg = AppStore|Any CPU
34 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.Ad-Hoc|Any CPU.Build.0 = AppStore|Any CPU
35 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
36 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
37 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.Ad-Hoc|iPhoneSimulator.ActiveCfg = AppStore|Any CPU
38 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.Ad-Hoc|iPhoneSimulator.Build.0 = AppStore|Any CPU
39 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.AppStore|Any CPU.ActiveCfg = AppStore|Any CPU
40 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.AppStore|Any CPU.Build.0 = AppStore|Any CPU
41 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
42 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.AppStore|iPhone.Build.0 = Debug|Any CPU
43 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|Any CPU
44 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.AppStore|iPhoneSimulator.Build.0 = AppStore|Any CPU
45 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
46 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.Debug|Any CPU.Build.0 = Debug|Any CPU
47 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.Debug|iPhone.ActiveCfg = Debug|Any CPU
48 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.Debug|iPhone.Build.0 = Debug|Any CPU
49 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
50 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
51 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.Release|Any CPU.ActiveCfg = Release|Any CPU
52 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.Release|Any CPU.Build.0 = Release|Any CPU
53 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.Release|iPhone.ActiveCfg = Release|Any CPU
54 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.Release|iPhone.Build.0 = Release|Any CPU
55 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
56 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
57 | {150D8A38-4419-4330-A42B-12879ACB7A54}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|iPhoneSimulator
58 | {150D8A38-4419-4330-A42B-12879ACB7A54}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone
59 | {150D8A38-4419-4330-A42B-12879ACB7A54}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone
60 | {150D8A38-4419-4330-A42B-12879ACB7A54}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator
61 | {150D8A38-4419-4330-A42B-12879ACB7A54}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator
62 | {150D8A38-4419-4330-A42B-12879ACB7A54}.AppStore|Any CPU.ActiveCfg = AppStore|iPhone
63 | {150D8A38-4419-4330-A42B-12879ACB7A54}.AppStore|Any CPU.Build.0 = AppStore|iPhone
64 | {150D8A38-4419-4330-A42B-12879ACB7A54}.AppStore|iPhone.ActiveCfg = AppStore|iPhone
65 | {150D8A38-4419-4330-A42B-12879ACB7A54}.AppStore|iPhone.Build.0 = AppStore|iPhone
66 | {150D8A38-4419-4330-A42B-12879ACB7A54}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator
67 | {150D8A38-4419-4330-A42B-12879ACB7A54}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator
68 | {150D8A38-4419-4330-A42B-12879ACB7A54}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator
69 | {150D8A38-4419-4330-A42B-12879ACB7A54}.Debug|Any CPU.Build.0 = Debug|iPhoneSimulator
70 | {150D8A38-4419-4330-A42B-12879ACB7A54}.Debug|iPhone.ActiveCfg = Debug|iPhone
71 | {150D8A38-4419-4330-A42B-12879ACB7A54}.Debug|iPhone.Build.0 = Debug|iPhone
72 | {150D8A38-4419-4330-A42B-12879ACB7A54}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator
73 | {150D8A38-4419-4330-A42B-12879ACB7A54}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator
74 | {150D8A38-4419-4330-A42B-12879ACB7A54}.Release|Any CPU.ActiveCfg = Release|iPhoneSimulator
75 | {150D8A38-4419-4330-A42B-12879ACB7A54}.Release|Any CPU.Build.0 = Release|iPhoneSimulator
76 | {150D8A38-4419-4330-A42B-12879ACB7A54}.Release|iPhone.ActiveCfg = Release|iPhone
77 | {150D8A38-4419-4330-A42B-12879ACB7A54}.Release|iPhone.Build.0 = Release|iPhone
78 | {150D8A38-4419-4330-A42B-12879ACB7A54}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
79 | {150D8A38-4419-4330-A42B-12879ACB7A54}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
80 | EndGlobalSection
81 | GlobalSection(SolutionProperties) = preSolution
82 | HideSolutionNode = FALSE
83 | EndGlobalSection
84 | EndGlobal
85 |
--------------------------------------------------------------------------------
/TZStackView/Alignment.cs:
--------------------------------------------------------------------------------
1 | namespace TZStackView
2 | {
3 | ///
4 | /// Alignment, the layout transverse to the stacking axis.
5 | ///
6 | public enum Alignment
7 | {
8 | ///
9 | /// Align the leading and trailing edges of vertically stacked items
10 | /// or the top and bottom edges of horizontally stacked items tightly to the container.
11 | ///
12 | Fill = 0,
13 |
14 | ///
15 | /// Align the leading edges of vertically stacked items
16 | /// or the top edges of horizontally stacked items tightly to the relevant edge
17 | /// of the container.
18 | ///
19 | Leading = 1,
20 |
21 | ///
22 | /// Align the leading edges of vertically stacked items
23 | /// or the top edges of horizontally stacked items tightly to the relevant edge
24 | /// of the container.
25 | ///
26 | Top = Leading,
27 |
28 | FirstBaseline = 2,
29 |
30 | ///
31 | /// Center the items in a vertical stack horizontally
32 | /// or the items in a horizontal stack vertically
33 | ///
34 | Center = 3,
35 |
36 | ///
37 | /// Align the trailing edges of vertically stacked items
38 | /// or the bottom edges of horizontally stacked items tightly to the relevant
39 | /// edge of the container
40 | ///
41 | Trailing = 4,
42 |
43 | ///
44 | /// Align the trailing edges of vertically stacked items
45 | /// or the bottom edges of horizontally stacked items tightly to the relevant
46 | /// edge of the container
47 | ///
48 | Bottom = Trailing
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/TZStackView/AnimationDelegate.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using CoreAnimation;
3 |
4 | namespace TZStackView
5 | {
6 | public class AnimationDelegate : CAAnimationDelegate
7 | {
8 | public Action AnimationStoppedCallback { get; set;}
9 |
10 | public override void AnimationStopped (CAAnimation anim, bool finished)
11 | {
12 | AnimationStoppedCallback?.Invoke ();
13 | }
14 | }
15 | }
16 |
17 |
--------------------------------------------------------------------------------
/TZStackView/Distribution.cs:
--------------------------------------------------------------------------------
1 | namespace TZStackView
2 | {
3 | ///
4 | /// Distribution. The layout along the stacking axis.
5 | /// All enum values fit first and last
6 | /// arranged subviews tightly to the container, except for
7 | /// , fit all items to
8 | /// when possible.
9 | ///
10 | public enum Distribution
11 | {
12 | ///
13 | /// When items do not fit (overflow) or fill (underflow) the space available
14 | /// adjustments occur according to CompressionResistance or hugging
15 | /// priorities of items, or when that is ambiguous, according to arrangement order.
16 | ///
17 | Fill = 0,
18 |
19 | ///
20 | /// Items are all the same size.
21 | /// When space allows, this will be the size of the item with the largest
22 | /// (along the axis of the stack).
23 | /// Overflow or underflow adjustments are distributed equally among the items.
24 | ///
25 | FillEqualy = 1,
26 |
27 | ///
28 | /// Overflow or underflow adjustments are distributed among the items proportional
29 | /// to their .
30 | ///
31 | FillProportionally = 2,
32 |
33 | ///
34 | /// Additional underflow spacing is divided equally in the spaces between the items.
35 | /// Overflow squeezing is controlled by compressionResistance priorities followed by
36 | /// arrangement order.
37 | ///
38 | EqualSpacing = 3,
39 |
40 | ///
41 | /// Equal center-to-center spacing of the items is maintained as much
42 | /// as possible while still maintaining a minimum edge-to-edge spacing within the
43 | /// allowed area.
44 | /// Additional underflow spacing is divided equally in the spacing. Overflow
45 | /// squeezing is distributed first according to CompressionResistance priorities
46 | /// of items, then according to subview order while maintaining the configured
47 | /// (edge-to-edge) spacing as a minimum.
48 | ///
49 | EqualCentering = 4
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/TZStackView/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 |
4 | // General Information about an assembly is controlled through the following
5 | // set of attributes. Change these attribute values to modify the information
6 | // associated with an assembly.
7 | [assembly: AssemblyTitle("TZStackView")]
8 | [assembly: AssemblyDescription("UIStackView backport for iOS 7 & 8 for Xamarin.iOS")]
9 | [assembly: AssemblyConfiguration("")]
10 | [assembly: AssemblyCompany("Tomasz Cielecki")]
11 | [assembly: AssemblyProduct("TZStackView")]
12 | [assembly: AssemblyCopyright("Copyright © Tomasz Cielecki (@cheesebaron) 2016")]
13 | [assembly: AssemblyTrademark("")]
14 | [assembly: AssemblyCulture("")]
15 |
16 | // Setting ComVisible to false makes the types in this assembly not visible
17 | // to COM components. If you need to access a type in this assembly from
18 | // COM, set the ComVisible attribute to true on that type.
19 | [assembly: ComVisible(false)]
20 |
21 | // The following GUID is for the ID of the typelib if this project is exposed to COM
22 | [assembly: Guid("efb28af7-e0ff-4957-82f8-0113d9bbfd8f")]
23 |
24 | // Version information for an assembly consists of the following four values:
25 | //
26 | // Major Version
27 | // Minor Version
28 | // Build Number
29 | // Revision
30 | //
31 | // You can specify all the values or you can default the Build and Revision Numbers
32 | // by using the '*' as shown below:
33 | // [assembly: AssemblyVersion("1.0.*")]
34 | [assembly: AssemblyVersion("1.0.0.0")]
35 | [assembly: AssemblyFileVersion("1.0.0.0")]
36 | [assembly: AssemblyInformationalVersion("0.0.2")]
37 |
--------------------------------------------------------------------------------
/TZStackView/SpacerView.cs:
--------------------------------------------------------------------------------
1 | using UIKit;
2 |
3 | namespace TZStackView
4 | {
5 | public class SpacerView : UIView
6 | {
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/TZStackView/StackView.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.Collections.Specialized;
5 | using System.ComponentModel;
6 | using System.Linq;
7 | using Foundation;
8 | using UIKit;
9 | using CoreGraphics;
10 | using CoreAnimation;
11 |
12 | namespace TZStackView
13 | {
14 | [Register("TZStackView")]
15 | [DesignTimeVisible(true)]
16 | public class StackView : UIView
17 | {
18 | private readonly ObservableCollection _arrangedSubviews =
19 | new ObservableCollection();
20 | private readonly List _registeredHiddenObserverViews = new List();
21 | private readonly List _animatingToHiddenViews = new List();
22 | private readonly List _spacerViews = new List();
23 | private readonly List _stackViewConstraints =
24 | new List();
25 | private readonly List _subviewConstraints =
26 | new List();
27 | private Alignment _alignment = Alignment.Fill;
28 | private Distribution _distribution = Distribution.Fill;
29 | private UILayoutConstraintAxis _axis = UILayoutConstraintAxis.Horizontal;
30 |
31 | public Distribution Distribution
32 | {
33 | get { return _distribution; }
34 | set
35 | {
36 | _distribution = value;
37 | SetNeedsUpdateConstraints();
38 | }
39 | }
40 |
41 | [Export("Disribution"), Browsable(true)]
42 | public int DistributionValue
43 | {
44 | get { return (int)Distribution; }
45 | set { Distribution = (Distribution)value; }
46 | }
47 |
48 | public Alignment Alignment
49 | {
50 | get { return _alignment; }
51 | set
52 | {
53 | _alignment = value;
54 | SetNeedsUpdateConstraints();
55 | }
56 | }
57 |
58 | [Export("Alignment"), Browsable(true)]
59 | public int AlignmentValue
60 | {
61 | get { return (int)Alignment; }
62 | set { Alignment = (Alignment)value; }
63 | }
64 |
65 | public UILayoutConstraintAxis Axis
66 | {
67 | get { return _axis; }
68 | set
69 | {
70 | _axis = value;
71 | SetNeedsUpdateConstraints();
72 | }
73 | }
74 |
75 | [Export("Axis"), Browsable(true)]
76 | public int AxisValue
77 | {
78 | get { return (int)Axis; }
79 | set { Axis = (UILayoutConstraintAxis)value; }
80 | }
81 |
82 | public IEnumerable ArrangedSubviews => _arrangedSubviews;
83 |
84 | [Export("LayoutMarginsRelative"), Browsable(true)]
85 | public bool LayoutMarginsRelativeArrangement { get; set; } = false;
86 |
87 | [Export("Spacing"), Browsable(true)]
88 | public float Spacing { get; set; } = 0f;
89 |
90 | private void ArrangedSubviewsChanged(object s,
91 | NotifyCollectionChangedEventArgs args)
92 | {
93 | var oldItems = args.OldItems;
94 | if (oldItems != null)
95 | foreach (var subview in oldItems.OfType())
96 | RemoveHiddenListener(subview);
97 |
98 | var newItems = args.NewItems;
99 | if (newItems != null)
100 | foreach(var subview in newItems.OfType())
101 | AddHiddenListener(subview);
102 | }
103 |
104 | public StackView(IntPtr handle) : base(handle) { }
105 |
106 | public StackView(IEnumerable arrangedSubviews = null)
107 | : base(CGRect.Empty)
108 | {
109 | _arrangedSubviews.CollectionChanged += ArrangedSubviewsChanged;
110 | Initialize(arrangedSubviews);
111 | }
112 |
113 | public StackView(CGRect frame)
114 | : base(frame)
115 | {
116 | _arrangedSubviews.CollectionChanged += ArrangedSubviewsChanged;
117 | Initialize();
118 | }
119 |
120 | public override void AwakeFromNib()
121 | {
122 | foreach (var constraint in Constraints)
123 | {
124 | if (constraint.GetType().Name == "NSIBPrototypingLayoutConstraint")
125 | {
126 | RemoveConstraint(constraint);
127 | }
128 | }
129 |
130 | foreach (var view in Subviews)
131 | AddArrangedSubview(view);
132 | }
133 |
134 | public override void PrepareForInterfaceBuilder()
135 | {
136 | base.PrepareForInterfaceBuilder();
137 |
138 | foreach (var view in Subviews)
139 | AddArrangedSubview(view);
140 | }
141 |
142 | private void Initialize(IEnumerable arrangedSubviews = null)
143 | {
144 | if (arrangedSubviews == null)
145 | arrangedSubviews = new List();
146 |
147 | foreach (var subview in arrangedSubviews)
148 | {
149 | subview.TranslatesAutoresizingMaskIntoConstraints = false;
150 | AddSubview(subview);
151 | _arrangedSubviews.Add(subview);
152 | }
153 | }
154 |
155 | private void AddHiddenListener(UIView view)
156 | {
157 | view.Layer.AddObserver(this, "hidden", NSKeyValueObservingOptions.OldNew, IntPtr.Zero);
158 | _registeredHiddenObserverViews.Add(view);
159 | }
160 |
161 | private void RemoveHiddenListener(UIView view)
162 | {
163 | if (_registeredHiddenObserverViews.Contains(view))
164 | {
165 | view.Layer.RemoveObserver(this, (NSString)"hidden", IntPtr.Zero);
166 | _registeredHiddenObserverViews.Remove(view);
167 | }
168 | }
169 |
170 | public override void ObserveValue(NSString keyPath, NSObject ofObject, NSDictionary change,
171 | IntPtr context)
172 | {
173 | if (keyPath != "hidden") return;
174 |
175 | var layer = ofObject as CALayer;
176 |
177 | // Delegate/WeakDelegate is usually the UIView on CALayer
178 | var view = layer?.WeakDelegate as UIView;
179 | if (view == null)
180 | return;
181 |
182 | var hidden = view.Hidden;
183 | var previousValue = (change["old"] as NSNumber)?.BoolValue;
184 | if (previousValue.HasValue &&
185 | previousValue.Value == hidden)
186 | {
187 | return;
188 | }
189 |
190 | if (hidden)
191 | {
192 | _animatingToHiddenViews.Add(view);
193 | }
194 |
195 | SetNeedsUpdateConstraints();
196 | SetNeedsLayout();
197 | LayoutIfNeeded();
198 |
199 | RemoveHiddenListener(view);
200 | //view.Hidden = false;
201 |
202 | var hidingAnimation = layer.AnimationForKey ("bounds.size");
203 |
204 | Action animationFinished = () => {
205 | var strongLayer = layer;
206 | strongLayer.Hidden = hidden;
207 | var strongView = view;
208 | if (_animatingToHiddenViews.Contains(strongView)){
209 | _animatingToHiddenViews.Remove(strongView);
210 | }
211 | AddHiddenListener(strongView);
212 | };
213 |
214 | if (hidingAnimation != null)
215 | {
216 | var group = new CAAnimationGroup ();
217 | group.Animations = new CAAnimation[0];
218 | group.Delegate = new AnimationDelegate {
219 | AnimationStoppedCallback = animationFinished
220 | };
221 |
222 | layer.AddAnimation (group, "TZSV-hidden-callback");
223 | }
224 | else{
225 | animationFinished ();
226 | }
227 | }
228 |
229 | public void AddArrangedSubview(UIView view)
230 | {
231 | view.TranslatesAutoresizingMaskIntoConstraints = false;
232 | AddSubview(view);
233 | _arrangedSubviews.Add(view);
234 | }
235 |
236 | public void RemoveArrangedSubview(UIView view)
237 | {
238 | if (_arrangedSubviews.Contains(view))
239 | _arrangedSubviews.Remove(view);
240 | }
241 |
242 | public void InsertArrangedSubview(UIView view, int atIndex)
243 | {
244 | view.TranslatesAutoresizingMaskIntoConstraints = false;
245 | AddSubview(view);
246 | _arrangedSubviews.Insert(atIndex, view);
247 | }
248 |
249 | public override void WillRemoveSubview(UIView view)
250 | {
251 | RemoveArrangedSubview(view);
252 | }
253 |
254 | public override void UpdateConstraints()
255 | {
256 | RemoveConstraints(_stackViewConstraints.ToArray());
257 | _stackViewConstraints.Clear();
258 |
259 | foreach (var arrangedSubview in _arrangedSubviews)
260 | arrangedSubview.RemoveConstraints(_subviewConstraints.ToArray());
261 | _subviewConstraints.Clear();
262 |
263 | foreach (var arrangedSubview in _arrangedSubviews)
264 | {
265 | if (Alignment != Alignment.Fill)
266 | {
267 | NSLayoutConstraint guideConstraint = null;
268 | if (Axis == UILayoutConstraintAxis.Horizontal)
269 | {
270 | guideConstraint = Constraint(arrangedSubview, NSLayoutAttribute.Height,
271 | view2: null, attr2: NSLayoutAttribute.NoAttribute, constant: 0, priority: 25);
272 | }
273 | else if (Axis == UILayoutConstraintAxis.Vertical)
274 | {
275 | guideConstraint = Constraint(arrangedSubview, NSLayoutAttribute.Width,
276 | view2: null, attr2: NSLayoutAttribute.NoAttribute, constant: 0, priority: 25);
277 | }
278 |
279 | _subviewConstraints.Add(guideConstraint);
280 | arrangedSubview.AddConstraint(guideConstraint);
281 | }
282 |
283 | if (IsHidden(arrangedSubview))
284 | {
285 | NSLayoutConstraint hiddenConstraint = null;
286 | if (Axis == UILayoutConstraintAxis.Horizontal)
287 | {
288 | hiddenConstraint = Constraint(arrangedSubview, NSLayoutAttribute.Width,
289 | view2: null, attr2: NSLayoutAttribute.NoAttribute);
290 | }
291 | else if (Axis == UILayoutConstraintAxis.Vertical)
292 | {
293 | hiddenConstraint = Constraint(arrangedSubview, NSLayoutAttribute.Height,
294 | view2: null, attr2: NSLayoutAttribute.NoAttribute);
295 | }
296 | _subviewConstraints.Add(hiddenConstraint);
297 | arrangedSubview.AddConstraint(hiddenConstraint);
298 | }
299 | }
300 |
301 | foreach(var spacerView in _spacerViews)
302 | spacerView.RemoveFromSuperview();
303 | _spacerViews.Clear();
304 |
305 | if (_arrangedSubviews.Any())
306 | {
307 | var visibleArrangedSubviews = _arrangedSubviews.Where(v => !IsHidden(v)).ToArray();
308 |
309 | switch (Distribution)
310 | {
311 | case Distribution.Fill:
312 | case Distribution.FillEqualy:
313 | case Distribution.FillProportionally: {
314 | if (Alignment != Alignment.Fill || LayoutMarginsRelativeArrangement)
315 | AddSpacerView();
316 |
317 | _stackViewConstraints.AddRange(CreateMatchEdgesConstraints(_arrangedSubviews));
318 | _stackViewConstraints.AddRange(CreateFirstAndLastViewMatchEdgesConstraints());
319 |
320 | if (Alignment == Alignment.FirstBaseline &&
321 | Axis == UILayoutConstraintAxis.Horizontal)
322 | _stackViewConstraints.Add(Constraint(this, NSLayoutAttribute.Height,
323 | attr2: NSLayoutAttribute.NoAttribute, priority: 49));
324 |
325 | if (Distribution == Distribution.FillEqualy)
326 | _stackViewConstraints.AddRange(CreateFillEquallyConstraints(_arrangedSubviews));
327 | if (Distribution == Distribution.FillProportionally)
328 | _stackViewConstraints.AddRange(
329 | CreateFillProportionallyConstraints(_arrangedSubviews));
330 |
331 | _stackViewConstraints.AddRange(CreateFillConstraints(_arrangedSubviews,
332 | constant: Spacing));
333 | break;
334 | }
335 | case Distribution.EqualSpacing: {
336 | var views = new List();
337 | var index = 0;
338 | foreach (var arrangedSubview in _arrangedSubviews)
339 | {
340 | if (IsHidden(arrangedSubview))
341 | continue;
342 | if (index > 0)
343 | views.Add(AddSpacerView());
344 | views.Add(arrangedSubview);
345 | index++;
346 | }
347 | if (_spacerViews.Count == 0)
348 | AddSpacerView();
349 |
350 | _stackViewConstraints.AddRange(CreateMatchEdgesConstraints(_arrangedSubviews));
351 | _stackViewConstraints.AddRange(CreateFirstAndLastViewMatchEdgesConstraints());
352 |
353 | if (Axis == UILayoutConstraintAxis.Horizontal)
354 | {
355 | _stackViewConstraints.Add(Constraint(this, NSLayoutAttribute.Width,
356 | attr2: NSLayoutAttribute.NoAttribute, priority: 49));
357 | if (Alignment == Alignment.FirstBaseline)
358 | _stackViewConstraints.Add(Constraint(this, NSLayoutAttribute.Height,
359 | attr2: NSLayoutAttribute.NoAttribute, priority: 49));
360 | }
361 | else if (Axis == UILayoutConstraintAxis.Vertical)
362 | _stackViewConstraints.Add(Constraint(this, NSLayoutAttribute.Height, attr2: NSLayoutAttribute.NoAttribute, priority: 49));
363 |
364 | _stackViewConstraints.AddRange(CreateFillConstraints(views));
365 | _stackViewConstraints.AddRange(CreateFillEquallyConstraints(_spacerViews));
366 | _stackViewConstraints.AddRange(CreateFillConstraints(_arrangedSubviews,
367 | relatedBy: NSLayoutRelation.GreaterThanOrEqual, constant: Spacing));
368 | break;
369 | }
370 | case Distribution.EqualCentering: {
371 | for (var i = 0; i < visibleArrangedSubviews.Length; i++)
372 | if (i > 0) AddSpacerView();
373 |
374 | if (_spacerViews.Count == 0)
375 | AddSpacerView();
376 |
377 | _stackViewConstraints.AddRange(CreateMatchEdgesConstraints(_arrangedSubviews));
378 | _stackViewConstraints.AddRange(CreateFirstAndLastViewMatchEdgesConstraints());
379 |
380 | if (Axis == UILayoutConstraintAxis.Horizontal)
381 | {
382 | _stackViewConstraints.Add(Constraint(this, NSLayoutAttribute.Width,
383 | attr2: NSLayoutAttribute.NoAttribute, priority: 49));
384 | if (Alignment == Alignment.FirstBaseline)
385 | _stackViewConstraints.Add(Constraint(this, NSLayoutAttribute.Height,
386 | attr2: NSLayoutAttribute.NoAttribute, priority: 49));
387 | }
388 | else if (Axis == UILayoutConstraintAxis.Vertical)
389 | _stackViewConstraints.Add(Constraint(this, NSLayoutAttribute.Height,
390 | attr2: NSLayoutAttribute.NoAttribute, priority: 49));
391 |
392 | UIView previousArrangedSubview = null;
393 | var index = 0;
394 | foreach (var arrangedSubview in visibleArrangedSubviews)
395 | {
396 | if (previousArrangedSubview != null)
397 | {
398 | var spacerView = _spacerViews[index - 1];
399 |
400 | if (Axis == UILayoutConstraintAxis.Horizontal)
401 | {
402 | _stackViewConstraints.Add(Constraint(previousArrangedSubview,
403 | NSLayoutAttribute.CenterX, view2: spacerView,
404 | attr2: NSLayoutAttribute.Leading));
405 | _stackViewConstraints.Add(Constraint(arrangedSubview,
406 | NSLayoutAttribute.CenterX, view2: spacerView,
407 | attr2: NSLayoutAttribute.Trailing));
408 | }
409 | else if (Axis == UILayoutConstraintAxis.Vertical)
410 | {
411 | _stackViewConstraints.Add(Constraint(previousArrangedSubview,
412 | NSLayoutAttribute.CenterY, view2: spacerView,
413 | attr2: NSLayoutAttribute.Top));
414 | _stackViewConstraints.Add(Constraint(arrangedSubview,
415 | NSLayoutAttribute.CenterY, view2: spacerView,
416 | attr2: NSLayoutAttribute.Bottom));
417 | }
418 | }
419 |
420 | previousArrangedSubview = arrangedSubview;
421 | index++;
422 | }
423 |
424 | _stackViewConstraints.AddRange(CreateFillEquallyConstraints(_spacerViews,
425 | priority: 150));
426 | _stackViewConstraints.AddRange(CreateFillConstraints(_arrangedSubviews,
427 | relatedBy: NSLayoutRelation.GreaterThanOrEqual, constant: Spacing));
428 | break;
429 | }
430 | }
431 |
432 | if (_spacerViews.Any())
433 | _stackViewConstraints.AddRange(CreateSurroundingSpacerViewConstraints(
434 | _spacerViews[0], views: visibleArrangedSubviews));
435 |
436 | if (LayoutMarginsRelativeArrangement && _spacerViews.Any())
437 | {
438 | var first = _spacerViews[0];
439 | _stackViewConstraints.Add(Constraint(this, NSLayoutAttribute.BottomMargin, view2: first));
440 | _stackViewConstraints.Add(Constraint(this, NSLayoutAttribute.LeftMargin, view2: first));
441 | _stackViewConstraints.Add(Constraint(this, NSLayoutAttribute.TopMargin, view2: first));
442 | _stackViewConstraints.Add(Constraint(this, NSLayoutAttribute.RightMargin, view2: first));
443 | }
444 | AddConstraints(_stackViewConstraints.ToArray());
445 | }
446 |
447 | base.UpdateConstraints();
448 | }
449 |
450 | private SpacerView AddSpacerView()
451 | {
452 | var spacerView = new SpacerView {TranslatesAutoresizingMaskIntoConstraints = false};
453 |
454 | _spacerViews.Add(spacerView);
455 | InsertSubview(spacerView, 0);
456 |
457 | return spacerView;
458 | }
459 |
460 | private IEnumerable CreateSurroundingSpacerViewConstraints(UIView spacerView,
461 | IEnumerable views)
462 | {
463 | if (Alignment == Alignment.Fill)
464 | return new NSLayoutConstraint[0];
465 |
466 | var topPriority = 1000f;
467 | var topRelation = NSLayoutRelation.LessThanOrEqual;
468 |
469 | var bottomPriority = 1000f;
470 | var bottomRelation = NSLayoutRelation.GreaterThanOrEqual;
471 |
472 | if (Alignment == Alignment.Top || Alignment == Alignment.Leading)
473 | {
474 | topPriority = 999.5f;
475 | topRelation = NSLayoutRelation.Equal;
476 | }
477 |
478 | if (Alignment == Alignment.Bottom || Alignment == Alignment.Trailing)
479 | {
480 | bottomPriority = 999.5f;
481 | bottomRelation = NSLayoutRelation.Equal;
482 | }
483 |
484 | var constraints = new List();
485 | foreach (var view in views)
486 | {
487 | if (Axis == UILayoutConstraintAxis.Horizontal)
488 | {
489 | constraints.Add(Constraint(spacerView, NSLayoutAttribute.Top, topRelation, view,
490 | priority: topPriority));
491 | constraints.Add(Constraint(spacerView, NSLayoutAttribute.Bottom, bottomRelation, view,
492 | priority: bottomPriority));
493 | }
494 | else if (Axis == UILayoutConstraintAxis.Vertical)
495 | {
496 | constraints.Add(Constraint(spacerView, NSLayoutAttribute.Leading, topRelation, view,
497 | priority: topPriority));
498 | constraints.Add(Constraint(spacerView, NSLayoutAttribute.Trailing, bottomRelation, view,
499 | priority: bottomPriority));
500 | }
501 | }
502 |
503 | if (Axis == UILayoutConstraintAxis.Horizontal)
504 | constraints.Add(Constraint(spacerView, NSLayoutAttribute.Height,
505 | attr2: NSLayoutAttribute.NoAttribute, constant: 0, priority: 51));
506 | else if (Axis == UILayoutConstraintAxis.Vertical)
507 | constraints.Add(Constraint(spacerView, NSLayoutAttribute.Width,
508 | attr2: NSLayoutAttribute.NoAttribute, constant: 0, priority: 51));
509 |
510 | return constraints;
511 | }
512 |
513 | private IEnumerable CreateFillProportionallyConstraints(
514 | IEnumerable views)
515 | {
516 | var viewss = views.ToArray();
517 | var constraints = new List();
518 |
519 | nfloat totalSize = 0f;
520 | var totalCount = 0;
521 |
522 | foreach (var subview in viewss)
523 | {
524 | if (IsHidden(subview)) continue;
525 |
526 | if (Axis == UILayoutConstraintAxis.Horizontal)
527 | totalSize += subview.IntrinsicContentSize.Width;
528 | else if (Axis == UILayoutConstraintAxis.Vertical)
529 | totalSize += subview.IntrinsicContentSize.Height;
530 | totalCount++;
531 | }
532 |
533 | totalSize += (totalCount - 1) * Spacing;
534 |
535 | if (totalSize <= 0)
536 | totalSize = 1;
537 |
538 | var priority = 1000f;
539 | var countDownPriority = viewss.Count(v => !IsHidden(v)) > 1;
540 |
541 | foreach (var subview in viewss)
542 | {
543 | if (countDownPriority)
544 | priority--;
545 |
546 | if (IsHidden(subview)) continue;
547 |
548 | if (Axis == UILayoutConstraintAxis.Horizontal)
549 | {
550 | var multiplier = subview.IntrinsicContentSize.Width/totalSize;
551 | constraints.Add(Constraint(subview, NSLayoutAttribute.Width,
552 | multiplier: (float) multiplier, priority: priority, view2: this));
553 | }
554 | else if (Axis == UILayoutConstraintAxis.Vertical)
555 | {
556 | var multiplier = subview.IntrinsicContentSize.Height / totalSize;
557 | constraints.Add(Constraint(subview, NSLayoutAttribute.Height,
558 | multiplier: (float)multiplier, priority: priority, view2: this));
559 | }
560 | }
561 |
562 | return constraints;
563 | }
564 |
565 | private IEnumerable CreateFillEquallyConstraints(
566 | IEnumerable views, float priority = 1000)
567 | {
568 | if (Axis == UILayoutConstraintAxis.Horizontal)
569 | return EqualAttributes(views.Where(v => !IsHidden(v)), attribute: NSLayoutAttribute.Width, priority: priority);
570 | return EqualAttributes(views.Where(v => !IsHidden(v)), attribute: NSLayoutAttribute.Height, priority: priority);
571 | }
572 |
573 | private IEnumerable CreateFillConstraints(
574 | IEnumerable views, float priority = 1000, NSLayoutRelation relatedBy = NSLayoutRelation.Equal, float constant = 0)
575 | {
576 | var constraints = new List();
577 |
578 | var viewss = views.ToArray();
579 |
580 | UIView previousView = null;
581 | foreach(var view in viewss)
582 | {
583 | if (previousView != null)
584 | {
585 | var c = 0f;
586 | if (!IsHidden(previousView) && !IsHidden(view))
587 | c = constant;
588 | else if (IsHidden(previousView) && !IsHidden(view) && !Equals(viewss.FirstOrDefault(), previousView))
589 | c = (constant / 2f);
590 | else if (!IsHidden(previousView) && IsHidden(view) && !Equals(viewss.LastOrDefault(), view))
591 | c = (constant / 2f);
592 |
593 | if (Axis == UILayoutConstraintAxis.Horizontal)
594 | constraints.Add(Constraint(view, NSLayoutAttribute.Leading, relatedBy, previousView, NSLayoutAttribute.Trailing, constant: c, priority: priority));
595 | else if (Axis == UILayoutConstraintAxis.Vertical)
596 | constraints.Add(Constraint(view, NSLayoutAttribute.Top, relatedBy, previousView,
597 | NSLayoutAttribute.Bottom, constant: c, priority: priority));
598 | }
599 | previousView = view;
600 | }
601 |
602 | return constraints;
603 | }
604 |
605 | private IEnumerable CreateMatchEdgesConstraints(
606 | IEnumerable views)
607 | {
608 | var constraints = new List();
609 |
610 | if (Axis == UILayoutConstraintAxis.Horizontal)
611 | {
612 | if (Alignment == Alignment.Fill)
613 | {
614 | constraints.AddRange(EqualAttributes(views, NSLayoutAttribute.Bottom));
615 | constraints.AddRange(EqualAttributes(views, NSLayoutAttribute.Top));
616 | }
617 | else if (Alignment == Alignment.Center)
618 | constraints.AddRange(EqualAttributes(views, NSLayoutAttribute.CenterY));
619 | else if (Alignment == Alignment.Leading || Alignment == Alignment.Top)
620 | constraints.AddRange(EqualAttributes(views, NSLayoutAttribute.Top));
621 | else if (Alignment == Alignment.Trailing || Alignment == Alignment.Bottom)
622 | constraints.AddRange(EqualAttributes(views, NSLayoutAttribute.Bottom));
623 | else if (Alignment == Alignment.FirstBaseline)
624 | constraints.AddRange(EqualAttributes(views, NSLayoutAttribute.FirstBaseline));
625 | }
626 | else if (Axis == UILayoutConstraintAxis.Vertical)
627 | {
628 | if (Alignment == Alignment.Fill)
629 | {
630 | constraints.AddRange(EqualAttributes(views, NSLayoutAttribute.Leading));
631 | constraints.AddRange(EqualAttributes(views, NSLayoutAttribute.Trailing));
632 | }
633 | else if (Alignment == Alignment.Center)
634 | constraints.AddRange(EqualAttributes(views, NSLayoutAttribute.CenterX));
635 | else if (Alignment == Alignment.Leading || Alignment == Alignment.Top)
636 | constraints.AddRange(EqualAttributes(views, NSLayoutAttribute.Leading));
637 | else if (Alignment == Alignment.Trailing || Alignment == Alignment.Bottom)
638 | constraints.AddRange(EqualAttributes(views, NSLayoutAttribute.Trailing));
639 | }
640 |
641 | return constraints;
642 | }
643 |
644 | private IEnumerable CreateFirstAndLastViewMatchEdgesConstraints()
645 | {
646 | var constraints = new List();
647 |
648 | var visibleViews = _arrangedSubviews.Where(v => !IsHidden(v)).ToArray();
649 | var firstView = visibleViews.FirstOrDefault();
650 | var lastView = visibleViews.LastOrDefault();
651 |
652 | var topView = _arrangedSubviews.FirstOrDefault();
653 | var bottomView = topView;
654 |
655 |
656 | if (_spacerViews.Any())
657 | {
658 | var spacerView = _spacerViews[0];
659 | if (Alignment == Alignment.Center)
660 | {
661 | topView = spacerView;
662 | bottomView = spacerView;
663 | }
664 | else if (Alignment == Alignment.Top || Alignment == Alignment.Leading)
665 | bottomView = spacerView;
666 | else if (Alignment == Alignment.Bottom || Alignment == Alignment.Trailing)
667 | topView = spacerView;
668 | else if (Alignment == Alignment.FirstBaseline)
669 | {
670 | if (Axis == UILayoutConstraintAxis.Horizontal)
671 | bottomView = spacerView;
672 | else if (Axis == UILayoutConstraintAxis.Vertical)
673 | {
674 | topView = spacerView;
675 | bottomView = spacerView;
676 | }
677 | }
678 | }
679 |
680 | var firstItem = LayoutMarginsRelativeArrangement ? _spacerViews.FirstOrDefault() : this;
681 |
682 | if (Axis == UILayoutConstraintAxis.Horizontal)
683 | {
684 | if (firstView != null)
685 | constraints.Add(Constraint(firstItem, NSLayoutAttribute.Leading, view2: firstView));
686 |
687 | if (lastView != null)
688 | constraints.Add(Constraint(firstItem, NSLayoutAttribute.Trailing, view2: lastView));
689 |
690 | constraints.Add(Constraint(firstItem, NSLayoutAttribute.Top, view2: topView));
691 | constraints.Add(Constraint(firstItem, NSLayoutAttribute.Bottom, view2: bottomView));
692 |
693 | if (Alignment == Alignment.Center)
694 | constraints.Add(Constraint(firstItem, NSLayoutAttribute.CenterY, view2: _arrangedSubviews.First()));
695 | }
696 | else if (Axis == UILayoutConstraintAxis.Vertical)
697 | {
698 | if (firstView != null)
699 | constraints.Add(Constraint(firstItem, NSLayoutAttribute.Top, view2: firstView));
700 |
701 | if (lastView != null)
702 | constraints.Add(Constraint(firstItem, NSLayoutAttribute.Bottom, view2: lastView));
703 |
704 | constraints.Add(Constraint(firstItem, NSLayoutAttribute.Leading, view2: topView));
705 | constraints.Add(Constraint(firstItem, NSLayoutAttribute.Trailing, view2: bottomView));
706 |
707 | if (Alignment == Alignment.Center)
708 | constraints.Add(Constraint(firstItem, NSLayoutAttribute.CenterX, view2: _arrangedSubviews.First()));
709 | }
710 |
711 | return constraints;
712 | }
713 |
714 | private IEnumerable EqualAttributes(IEnumerable views, NSLayoutAttribute attribute,
715 | float priority = 1000)
716 | {
717 | var currentPriority = priority;
718 | var constraints = new List();
719 |
720 | var viewss = views.ToArray();
721 |
722 | if (views != null && viewss.Any())
723 | {
724 | UIView firstView = null;
725 | var countDownPriority = currentPriority < 1000;
726 | foreach(var view in viewss)
727 | {
728 | if (firstView != null)
729 | constraints.Add(Constraint(firstView, attribute, view2: view, priority: currentPriority));
730 | else
731 | firstView = view;
732 |
733 | if (countDownPriority)
734 | currentPriority--;
735 | }
736 | }
737 |
738 | return constraints;
739 | }
740 |
741 | private NSLayoutConstraint Constraint(NSObject view1, NSLayoutAttribute attr1,
742 | NSLayoutRelation relation = NSLayoutRelation.Equal, NSObject view2 = null,
743 | NSLayoutAttribute? attr2 = null, float multiplier = 1, float constant = 0,
744 | float priority = 1000)
745 | {
746 | var attribute2 = attr2 ?? attr1;
747 |
748 | var constraint = NSLayoutConstraint.Create(view1, attr1, relation, view2, attribute2,
749 | multiplier, constant);
750 | constraint.Priority = priority;
751 | return constraint;
752 | }
753 |
754 | private bool IsHidden(UIView view)
755 | {
756 | if (view.Hidden)
757 | return true;
758 | return _animatingToHiddenViews.IndexOf(view) >= 0;
759 | }
760 | }
761 | }
762 |
--------------------------------------------------------------------------------
/TZStackView/TZStackView.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | AnyCPU
6 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}
7 | {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
8 | Library
9 | TZStackView
10 | Resources
11 | TZStackView
12 |
13 |
14 | true
15 | full
16 | false
17 | bin\Debug
18 | DEBUG;
19 | prompt
20 | 4
21 | false
22 |
23 |
24 | full
25 | true
26 | bin\Release
27 | prompt
28 | 4
29 | false
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | version: 1.0.{build}
2 | nuget:
3 | project_feed: true
4 | disable_publish_on_pr: true
5 | build_script:
6 | - ps: .\build.ps1
7 | deploy:
8 | - provider: NuGet
9 | api_key:
10 | secure: M6ahLYqDdpC2Whn/9Q/VU9AMb8P0rcz87+rmHDLYfi9Bl9gzGHA1vwL6Xh4DTfGR
11 | on:
12 | appveyor_repo_tag: true
--------------------------------------------------------------------------------
/assets/layout-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cheesebaron/TZStackView/6c887cac2cfd9fcefdf56260d263ad2ac5d25f5b/assets/layout-example.png
--------------------------------------------------------------------------------
/build.cake:
--------------------------------------------------------------------------------
1 | #tool "nuget:?package=GitVersion.CommandLine"
2 | #tool "nuget:?package=gitlink"
3 |
4 | var sln = new FilePath("TZStackView.sln");
5 | var project = new FilePath("TZStackView/TZStackView.csproj");
6 | var binDir = new DirectoryPath("TZStackView/bin/Release");
7 | var nuspec = new FilePath("TZStackView.nuspec");
8 | var outputDir = new DirectoryPath("artifacts");
9 | var target = Argument("target", "Default");
10 |
11 | var isRunningOnAppVeyor = AppVeyor.IsRunningOnAppVeyor;
12 | var isPullRequest = AppVeyor.Environment.PullRequest.IsPullRequest;
13 |
14 | Task("Clean").Does(() =>
15 | {
16 | CleanDirectories("./**/bin");
17 | CleanDirectories("./**/obj");
18 | CleanDirectories(outputDir.FullPath);
19 | });
20 |
21 | GitVersion versionInfo = null;
22 | Task("Version").Does(() => {
23 | GitVersion(new GitVersionSettings {
24 | UpdateAssemblyInfo = true,
25 | OutputType = GitVersionOutput.BuildServer
26 | });
27 |
28 | versionInfo = GitVersion(new GitVersionSettings{ OutputType = GitVersionOutput.Json });
29 | Information("VI:\t{0}", versionInfo.FullSemVer);
30 | });
31 |
32 | Task("Restore").Does(() => {
33 | NuGetRestore(sln);
34 | });
35 |
36 | Task("Build")
37 | .IsDependentOn("Clean")
38 | .IsDependentOn("Version")
39 | .IsDependentOn("Restore")
40 | .Does(() => {
41 |
42 | DotNetBuild(project,
43 | settings => settings.SetConfiguration("Release")
44 | .WithTarget("Build")
45 | );
46 | });
47 |
48 | Task("Package")
49 | .IsDependentOn("Build")
50 | .Does(() => {
51 | if (IsRunningOnWindows()) //pdbstr.exe and costura are not xplat currently
52 | GitLink(sln.GetDirectory(), new GitLinkSettings {
53 | RepositoryUrl = "https://github.com/Cheesebaron/TZStackView",
54 | ArgumentCustomization = args => args.Append("-ignore Sample")
55 | });
56 |
57 | EnsureDirectoryExists(outputDir);
58 |
59 | var files = GetFiles(binDir + "/**/*")
60 | - GetFiles(binDir + "/**/*.mdb"); // no mdb files please
61 |
62 | var nugetContent = new List();
63 | foreach(var dll in files){
64 | Information("File: {0}", dll.ToString());
65 | nugetContent.Add(new NuSpecContent {
66 | Target = "lib/Xamarin.iOS10",
67 | Source = dll.ToString()
68 | });
69 | }
70 |
71 | Information("File Count {0}", nugetContent.Count);
72 |
73 | NuGetPack(nuspec, new NuGetPackSettings {
74 | Authors = new [] { "Tomasz Cielecki" },
75 | Owners = new [] { "Tomasz Cielecki" },
76 | IconUrl = new Uri("http://i.imgur.com/V3983YY.png"),
77 | ProjectUrl = new Uri("https://github.com/Cheesebaron/TZStackView"),
78 | LicenseUrl = new Uri("https://github.com/Cheesebaron/TZStackView/blob/master/LICENSE"),
79 | Copyright = "Copyright (c) Tomasz Cielecki",
80 | RequireLicenseAcceptance = false,
81 | Tags = new [] {"xamarin", "monotouch", "ios", "stackview"},
82 | Version = versionInfo.NuGetVersion,
83 | Symbols = false,
84 | NoPackageAnalysis = true,
85 | OutputDirectory = outputDir,
86 | Verbosity = NuGetVerbosity.Detailed,
87 | Files = nugetContent,
88 | BasePath = "/.",
89 | ReleaseNotes = ParseReleaseNotes("./releasenotes.md").Notes.ToArray()
90 | });
91 | });
92 |
93 | Task("UploadAppVeyorArtifact")
94 | .IsDependentOn("Package")
95 | .WithCriteria(() => isRunningOnAppVeyor)
96 | .Does(() => {
97 |
98 | Information("Artifacts Dir: {0}", outputDir.FullPath);
99 |
100 | foreach(var file in GetFiles(outputDir.FullPath + "/*")) {
101 | Information("Uploading {0}", file.FullPath);
102 | AppVeyor.UploadArtifact(file.FullPath);
103 | }
104 | });
105 |
106 | Task("Default").IsDependentOn("UploadAppVeyorArtifact").Does(() => {});
107 |
108 | RunTarget(target);
--------------------------------------------------------------------------------
/build.ps1:
--------------------------------------------------------------------------------
1 | ##########################################################################
2 | # This is the Cake bootstrapper script for PowerShell.
3 | # This file was downloaded from https://github.com/cake-build/resources
4 | # Feel free to change this file to fit your needs.
5 | ##########################################################################
6 |
7 | <#
8 |
9 | .SYNOPSIS
10 | This is a Powershell script to bootstrap a Cake build.
11 |
12 | .DESCRIPTION
13 | This Powershell script will download NuGet if missing, restore NuGet tools (including Cake)
14 | and execute your Cake build script with the parameters you provide.
15 |
16 | .PARAMETER Script
17 | The build script to execute.
18 | .PARAMETER Target
19 | The build script target to run.
20 | .PARAMETER Configuration
21 | The build configuration to use.
22 | .PARAMETER Verbosity
23 | Specifies the amount of information to be displayed.
24 | .PARAMETER Experimental
25 | Tells Cake to use the latest Roslyn release.
26 | .PARAMETER WhatIf
27 | Performs a dry run of the build script.
28 | No tasks will be executed.
29 | .PARAMETER Mono
30 | Tells Cake to use the Mono scripting engine.
31 | .PARAMETER SkipToolPackageRestore
32 | Skips restoring of packages.
33 | .PARAMETER ScriptArgs
34 | Remaining arguments are added here.
35 |
36 | .LINK
37 | http://cakebuild.net
38 |
39 | #>
40 |
41 | [CmdletBinding()]
42 | Param(
43 | [string]$Script = "build.cake",
44 | [string]$Target = "Default",
45 | [ValidateSet("Release", "Debug")]
46 | [string]$Configuration = "Release",
47 | [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")]
48 | [string]$Verbosity = "Verbose",
49 | [switch]$Experimental,
50 | [Alias("DryRun","Noop")]
51 | [switch]$WhatIf,
52 | [switch]$Mono,
53 | [switch]$SkipToolPackageRestore,
54 | [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)]
55 | [string[]]$ScriptArgs
56 | )
57 |
58 | [Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null
59 | function MD5HashFile([string] $filePath)
60 | {
61 | if ([string]::IsNullOrEmpty($filePath) -or !(Test-Path $filePath -PathType Leaf))
62 | {
63 | return $null
64 | }
65 |
66 | [System.IO.Stream] $file = $null;
67 | [System.Security.Cryptography.MD5] $md5 = $null;
68 | try
69 | {
70 | $md5 = [System.Security.Cryptography.MD5]::Create()
71 | $file = [System.IO.File]::OpenRead($filePath)
72 | return [System.BitConverter]::ToString($md5.ComputeHash($file))
73 | }
74 | finally
75 | {
76 | if ($file -ne $null)
77 | {
78 | $file.Dispose()
79 | }
80 | }
81 | }
82 |
83 | Write-Host "Preparing to run build script..."
84 |
85 | if(!$PSScriptRoot){
86 | $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
87 | }
88 |
89 | $TOOLS_DIR = Join-Path $PSScriptRoot "tools"
90 | $NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe"
91 | $CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe"
92 | $NUGET_URL = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe"
93 | $PACKAGES_CONFIG = Join-Path $TOOLS_DIR "packages.config"
94 | $PACKAGES_CONFIG_MD5 = Join-Path $TOOLS_DIR "packages.config.md5sum"
95 |
96 | # Should we use mono?
97 | $UseMono = "";
98 | if($Mono.IsPresent) {
99 | Write-Verbose -Message "Using the Mono based scripting engine."
100 | $UseMono = "-mono"
101 | }
102 |
103 | # Should we use the new Roslyn?
104 | $UseExperimental = "";
105 | if($Experimental.IsPresent -and !($Mono.IsPresent)) {
106 | Write-Verbose -Message "Using experimental version of Roslyn."
107 | $UseExperimental = "-experimental"
108 | }
109 |
110 | # Is this a dry run?
111 | $UseDryRun = "";
112 | if($WhatIf.IsPresent) {
113 | $UseDryRun = "-dryrun"
114 | }
115 |
116 | # Make sure tools folder exists
117 | if ((Test-Path $PSScriptRoot) -and !(Test-Path $TOOLS_DIR)) {
118 | Write-Verbose -Message "Creating tools directory..."
119 | New-Item -Path $TOOLS_DIR -Type directory | out-null
120 | }
121 |
122 | # Make sure that packages.config exist.
123 | if (!(Test-Path $PACKAGES_CONFIG)) {
124 | Write-Verbose -Message "Downloading packages.config..."
125 | try { (New-Object System.Net.WebClient).DownloadFile("http://cakebuild.net/download/bootstrapper/packages", $PACKAGES_CONFIG) } catch {
126 | Throw "Could not download packages.config."
127 | }
128 | }
129 |
130 | # Try find NuGet.exe in path if not exists
131 | if (!(Test-Path $NUGET_EXE)) {
132 | Write-Verbose -Message "Trying to find nuget.exe in PATH..."
133 | $existingPaths = $Env:Path -Split ';' | Where-Object { (![string]::IsNullOrEmpty($_)) -and (Test-Path $_) }
134 | $NUGET_EXE_IN_PATH = Get-ChildItem -Path $existingPaths -Filter "nuget.exe" | Select -First 1
135 | if ($NUGET_EXE_IN_PATH -ne $null -and (Test-Path $NUGET_EXE_IN_PATH.FullName)) {
136 | Write-Verbose -Message "Found in PATH at $($NUGET_EXE_IN_PATH.FullName)."
137 | $NUGET_EXE = $NUGET_EXE_IN_PATH.FullName
138 | }
139 | }
140 |
141 | # Try download NuGet.exe if not exists
142 | if (!(Test-Path $NUGET_EXE)) {
143 | Write-Verbose -Message "Downloading NuGet.exe..."
144 | try {
145 | (New-Object System.Net.WebClient).DownloadFile($NUGET_URL, $NUGET_EXE)
146 | } catch {
147 | Throw "Could not download NuGet.exe."
148 | }
149 | }
150 |
151 | # Save nuget.exe path to environment to be available to child processed
152 | $ENV:NUGET_EXE = $NUGET_EXE
153 |
154 | # Restore tools from NuGet?
155 | if(-Not $SkipToolPackageRestore.IsPresent) {
156 | Push-Location
157 | Set-Location $TOOLS_DIR
158 |
159 | # Check for changes in packages.config and remove installed tools if true.
160 | [string] $md5Hash = MD5HashFile($PACKAGES_CONFIG)
161 | if((!(Test-Path $PACKAGES_CONFIG_MD5)) -Or
162 | ($md5Hash -ne (Get-Content $PACKAGES_CONFIG_MD5 ))) {
163 | Write-Verbose -Message "Missing or changed package.config hash..."
164 | Remove-Item * -Recurse -Exclude packages.config,nuget.exe
165 | }
166 |
167 | Write-Verbose -Message "Restoring tools from NuGet..."
168 | $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$TOOLS_DIR`""
169 |
170 | if ($LASTEXITCODE -ne 0) {
171 | Throw "An error occured while restoring NuGet tools."
172 | }
173 | else
174 | {
175 | $md5Hash | Out-File $PACKAGES_CONFIG_MD5 -Encoding "ASCII"
176 | }
177 | Write-Verbose -Message ($NuGetOutput | out-string)
178 | Pop-Location
179 | }
180 |
181 | # Make sure that Cake has been installed.
182 | if (!(Test-Path $CAKE_EXE)) {
183 | Throw "Could not find Cake.exe at $CAKE_EXE"
184 | }
185 |
186 | # Start Cake
187 | Write-Host "Running build script..."
188 | Invoke-Expression "& `"$CAKE_EXE`" `"$Script`" -target=`"$Target`" -configuration=`"$Configuration`" -verbosity=`"$Verbosity`" $UseMono $UseDryRun $UseExperimental $ScriptArgs"
189 | exit $LASTEXITCODE
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##########################################################################
4 | # This is the Cake bootstrapper script for Linux and OS X.
5 | # This file was downloaded from https://github.com/cake-build/resources
6 | # Feel free to change this file to fit your needs.
7 | ##########################################################################
8 |
9 | # Define directories.
10 | SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
11 | TOOLS_DIR=$SCRIPT_DIR/tools
12 | NUGET_EXE=$TOOLS_DIR/nuget.exe
13 | CAKE_EXE=$TOOLS_DIR/Cake/Cake.exe
14 | PACKAGES_CONFIG=$TOOLS_DIR/packages.config
15 | PACKAGES_CONFIG_MD5=$TOOLS_DIR/packages.config.md5sum
16 |
17 | # Define md5sum or md5 depending on Linux/OSX
18 | MD5_EXE=
19 | if [[ "$(uname -s)" == "Darwin" ]]; then
20 | MD5_EXE="md5 -r"
21 | else
22 | MD5_EXE="md5sum"
23 | fi
24 |
25 | # Define default arguments.
26 | SCRIPT="build.cake"
27 | TARGET="Default"
28 | CONFIGURATION="Release"
29 | VERBOSITY="verbose"
30 | DRYRUN=
31 | SHOW_VERSION=false
32 | SCRIPT_ARGUMENTS=()
33 |
34 | # Parse arguments.
35 | for i in "$@"; do
36 | case $1 in
37 | -s|--script) SCRIPT="$2"; shift ;;
38 | -t|--target) TARGET="$2"; shift ;;
39 | -c|--configuration) CONFIGURATION="$2"; shift ;;
40 | -v|--verbosity) VERBOSITY="$2"; shift ;;
41 | -d|--dryrun) DRYRUN="-dryrun" ;;
42 | --version) SHOW_VERSION=true ;;
43 | --) shift; SCRIPT_ARGUMENTS+=("$@"); break ;;
44 | *) SCRIPT_ARGUMENTS+=("$1") ;;
45 | esac
46 | shift
47 | done
48 |
49 | # Make sure the tools folder exist.
50 | if [ ! -d "$TOOLS_DIR" ]; then
51 | mkdir "$TOOLS_DIR"
52 | fi
53 |
54 | # Make sure that packages.config exist.
55 | if [ ! -f "$TOOLS_DIR/packages.config" ]; then
56 | echo "Downloading packages.config..."
57 | curl -Lsfo "$TOOLS_DIR/packages.config" http://cakebuild.net/download/bootstrapper/packages
58 | if [ $? -ne 0 ]; then
59 | echo "An error occured while downloading packages.config."
60 | exit 1
61 | fi
62 | fi
63 |
64 | # Download NuGet if it does not exist.
65 | if [ ! -f "$NUGET_EXE" ]; then
66 | echo "Downloading NuGet..."
67 | curl -Lsfo "$NUGET_EXE" https://dist.nuget.org/win-x86-commandline/latest/nuget.exe
68 | if [ $? -ne 0 ]; then
69 | echo "An error occured while downloading nuget.exe."
70 | exit 1
71 | fi
72 | fi
73 |
74 | # Restore tools from NuGet.
75 | pushd "$TOOLS_DIR" >/dev/null
76 | if [ ! -f $PACKAGES_CONFIG_MD5 ] || [ "$( cat $PACKAGES_CONFIG_MD5 | sed 's/\r$//' )" != "$( $MD5_EXE $PACKAGES_CONFIG | awk '{ print $1 }' )" ]; then
77 | find . -type d ! -name . | xargs rm -rf
78 | fi
79 |
80 | mono "$NUGET_EXE" install -ExcludeVersion
81 | if [ $? -ne 0 ]; then
82 | echo "Could not restore NuGet packages."
83 | exit 1
84 | fi
85 |
86 | $MD5_EXE $PACKAGES_CONFIG | awk '{ print $1 }' >| $PACKAGES_CONFIG_MD5
87 |
88 | popd >/dev/null
89 |
90 | # Make sure that Cake has been installed.
91 | if [ ! -f "$CAKE_EXE" ]; then
92 | echo "Could not find Cake.exe at '$CAKE_EXE'."
93 | exit 1
94 | fi
95 |
96 | # Start Cake
97 | if $SHOW_VERSION; then
98 | exec mono "$CAKE_EXE" -version
99 | else
100 | exec mono "$CAKE_EXE" $SCRIPT -verbosity=$VERBOSITY -configuration=$CONFIGURATION -target=$TARGET $DRYRUN "${SCRIPT_ARGUMENTS[@]}"
101 | fi
--------------------------------------------------------------------------------
/releasenotes.md:
--------------------------------------------------------------------------------
1 | ### New in 1.1.0
2 | - Fixed XIB/Storyboard support
3 | - Aligned enums with UIKit
4 |
5 | ### New in 1.1.1 (released 26/3/2017)
6 | - Fixed FillProportionally Distribution ([#7](https://github.com/Cheesebaron/TZStackView/pull/7))
--------------------------------------------------------------------------------
/tools/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------