<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[StepUp Development - Medium]]></title>
        <description><![CDATA[Read from our mobile development experiences. - Medium]]></description>
        <link>https://medium.com/stepup-development?source=rss----1ece39e3c702---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>StepUp Development - Medium</title>
            <link>https://medium.com/stepup-development?source=rss----1ece39e3c702---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Tue, 14 Jul 2026 15:24:58 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/stepup-development" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Pie Chart in SwiftUI]]></title>
            <link>https://medium.com/stepup-development/pie-chart-in-swiftui-24a9ec44d438?source=rss----1ece39e3c702---4</link>
            <guid isPermaLink="false">https://medium.com/p/24a9ec44d438</guid>
            <category><![CDATA[developer]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[swift]]></category>
            <dc:creator><![CDATA[Manuel Kunz]]></dc:creator>
            <pubDate>Sat, 03 Apr 2021 09:41:40 GMT</pubDate>
            <atom:updated>2021-04-09T20:06:47.863Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*W873X-xOLf83H6YALXyoLQ.jpeg" /></figure><p>Recently two friends and I were participating in a <a href="https://swiftuijam.com">SwiftUI Jam</a>. Over one weekend we were developing a little application in SwiftUI, which was a lot of fun.</p><p>The dashboard of the application shows multiple statistics, one of them presented as a pie chart.</p><p>In the following I want to show you the approach we took to make a simple reusable pie chart in SwiftUI.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XYyR4v-_TCwiKHm2Pkoy9Q.png" /></figure><p>At first I’m going to show you how to make a pie chart with fixed values to get the hang on how the slices are drawn. Further into the article I will show you our approach on making the pie chart easier to use and responsive in its size.</p><h3><strong>Drawing a circle</strong></h3><p>Drawing a circle in SwiftUI could not be easier.</p><pre>Circle()</pre><p>That’s it, but this will not take us far, so what we really need is drawing a slice of a pie in every size we want. So first, let’s take a look on how to draw for example a half circle. For this we are going to use paths.</p><p>I’m not going to show you how paths in general work, but with paths and arcs you can draw much more complex forms than circles.</p><h4><strong>Draw a half circle</strong></h4><p>The following code creates a half circle with a radius of 100 from degree 0 to degree 180.</p><p><em>.move</em> moves the path to the correct starting point. <em>.addArc</em> draws an arc around the given center between the two angles with the given radius.</p><p>This is all you need to draw a half circle.</p><pre>Path { path in<br>    path.move(to: CGPoint(x: 200, y: 200))<br>    path.addArc(center: CGPoint(x: 200, y: 200), <br>                radius: 100, <br>                startAngle: Angle(degrees: 0), <br>                endAngle: Angle(degrees: 180), <br>                clockwise: false)<br>}<br>.fill(Color.red)</pre><p>What sometimes can be a little bit tricky, at least it was for me, is the meaning of clockwise.</p><p>In the official <a href="https://developer.apple.com/documentation/uikit/uibezierpath/1624358-init#1965853">Apple Documentation</a> you can see an image of a circle to understand which degree is at which position of the circle. Clockwise is defined as ‘<strong>The direction in which to draw the arc</strong>’. So one, like me, would think of a clock we all know and would expect the code in the example above with clockwise false to draw an half circle with the top half filled red, but it is the complete opposite, you will get the lower half of the circle. It is a little bit confusing and might clash with the way SwiftUI handles coordinate systems as explained in this <a href="https://stackoverflow.com/a/57226474/7804792">answer</a> by <a href="https://stackoverflow.com/users/77567/rob-mayoff">rob-mayoff</a>.</p><p>For the following this will not be relevant because we will just work with what is given to us. I will choose clockwise as false, I’m sure this can also be done with clockwise true, but you will have to adjust the calculation later on.</p><h3><strong>Draw a Pie Chart with fixed values</strong></h3><p>But before we get to fancy calculations let’s draw our first pie chart.</p><pre>ZStack(alignment: .center) {<br>    Path { path in<br>        path.addArc(center: CGPoint(x: 200, y: 200), <br>                    radius: 100,<br>                    startAngle: Angle(degrees: 0), <br>                    endAngle: Angle(degrees: 180), <br>                    clockwise: false)<br>    }<br>    .fill(Color.red)</pre><pre>    Path { path in<br>        path.addArc(center: CGPoint(x: 200, y: 200), <br>                    radius: 100, <br>                    startAngle: Angle(degrees: 180), <br>                    endAngle: Angle(degrees: 360), <br>                    clockwise: false)<br>    }<br>    .fill(Color.blue)<br>}</pre><p>We put the <em>Path</em> from the example in a <em>ZStack</em> to place multiple elements above each other and adjusted the start and end <em>Angle</em> for the second <em>Path</em>, this results in a pie chart with two halfs.</p><p>For fixed values this is all you would need, but in most cases the values shown are not fixed so let’s have a look at how we can handle this.</p><h4><strong>Simplify the UI</strong></h4><p>Our pie chart will exist of multiple slices, I ended up calling them a <em>PieSlice</em>. In the example above each <em>Path</em> is one <em>PieSlice</em>, so we will wrap this in a struct:</p><pre>private struct PieSlice : Hashable {<br>    var start: Angle<br>    var end: Angle<br>    var color: Color<br>}</pre><p>In the body of the view, we can now iterate (that’s why we need the <em>PieSlice</em> to perform to <em>Hashable</em>) the pie slices, remove the fixed angles and the color.</p><pre>struct PieChart: View {<br>    private var slices = [<br>        PieSlice(start: Angle(degrees: 0), <br>                 end: Angle(degrees: 180), <br>                 color: .red),<br>        PieSlice(start: Angle(degrees: 180), <br>                 end: Angle(degrees: 360), <br>                 color: .blue)<br>    ]</pre><pre>    var body: some View {<br>        ZStack(alignment: .center) {<br>            ForEach(slices, id: \.self) { slice in<br>                Path { path in<br>                    path.move(to: CGPoint(x: 200, y: 200))<br>                    path.addArc(center: CGPoint(x: 200, y: 200),              <br>                                radius: 100, <br>                                startAngle: slice.start, <br>                                endAngle: slice.end, <br>                                clockwise: false<br>                    )<br>                }<br>                .fill(slice.color)<br>            }<br>        }<br>    }<br>}</pre><pre>private struct PieSlice : Hashable {<br>    var start: Angle<br>    var end: Angle<br>    var color: Color<br>}</pre><p>The pie chart can now easily be extended with more slices. The fixed values of radius and the center point will removed later on, we will now do some calculations to get rid off the fixed angles.</p><h3><strong>Calculation</strong></h3><p>When creating some kind of reusable component the saying of ‘<strong>Eat your own dog food</strong>’ always comes up to my mind. So I want to make my pie charts as easy to use as possible. The minimum of information we have to provide are the values and the color in which a value should be displayed (if the colors can be random you could also come up with some predefined colors or a RandomColorGenerator).</p><pre>init(_ values: [(Color, Double)]) {<br>    slices = calculateSlices(from: values)<br>}</pre><pre>// Usage for the example above</pre><pre>PieChart([<br>    (.red, 50),<br>    (.blue, 50)<br>])</pre><p>This is where our calculation has to come in. We have the given values that should be displayed and we need to convert these values into our slices. This will be done in the <em>calculateSlices </em>function.</p><pre>private func calculateSlices(from inputValues: [(color: Color, value: Double)]) -&gt; [PieSlice] {</pre><pre>    // 1<br>    let sumOfAllValues = inputValues.reduce(0) { $0 + $1.value }</pre><pre>    guard sumOfAllValues &gt; 0 else {<br>        return []<br>    }</pre><pre>    let degreeForOneValue = 360.0 / sumOfAllValues</pre><pre>    // 2<br>    var slices = [PieSlice]()<br>    var currentStartAngle = 0.0<br>    inputValues.forEach { inputValue in<br>        let endAngle = degreeForOneValue * inputValue.value + <br>                       currentStartAngle</pre><pre>        slices.append(<br>                 PieSlice(<br>                     start: Angle(degrees: currentStartAngle),  <br>                     end: Angle(degrees: endAngle), <br>                     color: inputValue.color<br>                 )<br>        )</pre><pre>        currentStartAngle = endAngle<br>    }<br>    return slices<br>}</pre><p>This might seem a bit complex at first but let’s break it down. <br>In the first section two calculations are done.</p><p>First we get the sum of all values. We will then devide 360.0 degrees by this sum.<br>This will give us the degrees which are needed to display a single value.</p><p>For example the input values are 20, 30 and 50. The sum of this values are 100.</p><pre>let degreeForOneValue = 360.0 / 100<br>// Will be 3.6</pre><p>In this example we will need 3.6 degrees to display the value 1. For 50 we will need <strong>3,6 * 50</strong> which is <strong>180</strong> this will take half of the circle.</p><p>In the second section of the calculation we iterate the input values and map them to pie slices. Additional to the conversion of value to degree we have to handle the start of each angle. Each <em>PieSlice</em> should start at the end angle of the previous <em>PieSlice</em>, this is handled with the <em>currentStartAngle</em> which will be added to each calculated end angle. At the end of each iteration the <em>currentStartAngle</em> will be set to the <em>endAngle</em> of the calculated <em>PieSlice</em>.</p><p>This is all we have to do to create a pie chart which is pretty simple to use. The example above with two half circles can now be achieved like this:</p><pre>PieChart([<br>    (.red, 50),<br>    (.blue, 50),<br>])</pre><pre>// or this</pre><pre>PieChart([<br>    (.red, 1),<br>    (.blue, 1),<br>])</pre><p>We can now create any pie chart. But we are not finished yet, our pie chart is dynamic in values but it is pretty fixed in its size with a hard coded center point and a radius which is fixed to 100.</p><h3><strong>Responsive size for the Pie Chart</strong></h3><p>We can get the size of the parent view by wrapping the <em>ZStack</em> into a <em>GeometryReader</em>. Our pie chart will be placed in some view, so it will be some kind of rectangle. This rectangle can have a different height and width in which we want to use as much space without overlapping.</p><p>So what do we need? First the center of the rectangle. Second the radius the pie chart can have to fit into the rectangle.</p><p>The center is the half of the width for x and the half of the height for y. These values can be accessed on the <em>GeometryReader</em>.</p><p>The height and width of the rectangle are most likely not the same value, except a square, we have to use the smaller of both values as radius, the pie chart then has the biggest size possible without overlapping.</p><pre>GeometryReader { reader in<br>    let halfWidth = (reader.size.width / 2)<br>    let halfHeight = (reader.size.height / 2)<br>    let radius = min(halfWidth, halfHeight)<br>    let center = CGPoint(x: halfWidth, y: halfHeight)</pre><pre>    ZStack(alignment: .center) {<br>        ForEach(slices, id: \.self) { slice in<br>            Path { path in<br>                path.move(to: center)<br>                path.addArc(center: center, <br>                            radius: radius, <br>                            startAngle: slice.start, <br>                            endAngle: slice.end, <br>                            clockwise: false<br>                )<br>            }<br>            .fill(slice.color)<br>        }<br>    }<br>}</pre><p>And that’s it. I’d love to hear any kind of feedback.</p><p>The example project can be found on <a href="https://github.com/KunzManuel/SwiftUI-PieChart">GitHub</a>. Show me what kind of reusable Views have you done with SwiftUI?</p><p>I hope you liked the article, the approach we chose and the way the pie chart can be used.</p><p>Feel free to contact me <a href="https://twitter.com/manuelkunz_sw">Twitter</a>.</p><p>Happy coding!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=24a9ec44d438" width="1" height="1" alt=""><hr><p><a href="https://medium.com/stepup-development/pie-chart-in-swiftui-24a9ec44d438">Pie Chart in SwiftUI</a> was originally published in <a href="https://medium.com/stepup-development">StepUp Development</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to use multiple users with git]]></title>
            <link>https://medium.com/stepup-development/how-to-use-multiple-users-with-git-5962e689b5f0?source=rss----1ece39e3c702---4</link>
            <guid isPermaLink="false">https://medium.com/p/5962e689b5f0</guid>
            <category><![CDATA[git]]></category>
            <category><![CDATA[gitlab]]></category>
            <category><![CDATA[automation]]></category>
            <category><![CDATA[github]]></category>
            <dc:creator><![CDATA[Manuel Kunz]]></dc:creator>
            <pubDate>Thu, 11 Feb 2021 16:59:14 GMT</pubDate>
            <atom:updated>2021-02-11T16:59:14.487Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*maurjo94aWjtzeoMZKkncA.jpeg" /></figure><p>Did you ever switch from a project for work to a private project and in the rush of coding you committed and pushed your code with the wrong user? Keep reading how to avoid this situation by setting up different users for different repositories.</p><p>So we are not going to start in a cage but let’s pretend we are Tony Stark and we are working on a private project and on the side we do commit for a little iron suit we are building. Let’s create the following structure of folders and files in our root directory. The <em>.gitconfig</em> file in the root directory might already exist. If not create it as well.</p><pre>├── Root/<br>│   ├── .gitconfig<br>│   ├── Private/<br>│   │   ├── .gitconfig<br>│   ├── IronMan/<br>│   │   ├── .gitconfig</pre><p>Let’s start with the the config files in our subfolders.</p><p>In the Private folder <em>~/Private/.gitconfig,</em> add the following:</p><pre>[user]<br>    name = Tony Stark<br>    email = stark.tony@starkindustries.com</pre><p>In the IronMan folder <em>~/IronMan/.gitconfig,</em> add the following:</p><pre>[user]<br>    name = Iron Man<br>    email = stark.tony@starkindustries.com</pre><p>Finally, in the <em>.gitconfig</em> in the root folder we will put all the things together.</p><pre>[user]<br>    name = Default Name<br>    email = default@mail.com</pre><pre>[includeIf &quot;gitdir:~/Private/&quot;]<br>    path = ~/Private/.gitconfig<br><br>[includeIf &quot;gitdir:~/IronMan/&quot;]<br>    path = ~/IronMan/.gitconfig</pre><p>The user here is your default user for all git configs. For all projects in IronMan the <em>~/IronMan/.gitconfig </em>will be applied and the user will be overwritten.</p><p>That’s it. You do not have to worry about commiting with the wrong user anymore. If you have any other ideas that simplify working with git let us know and give us feedback if you like to.</p><p>Enjoy coding.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5962e689b5f0" width="1" height="1" alt=""><hr><p><a href="https://medium.com/stepup-development/how-to-use-multiple-users-with-git-5962e689b5f0">How to use multiple users with git</a> was originally published in <a href="https://medium.com/stepup-development">StepUp Development</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Interface Builder tips and tricks]]></title>
            <link>https://medium.com/stepup-development/interface-builder-tips-and-tricks-f72e63aa6e5c?source=rss----1ece39e3c702---4</link>
            <guid isPermaLink="false">https://medium.com/p/f72e63aa6e5c</guid>
            <category><![CDATA[best-practices]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[xcode]]></category>
            <dc:creator><![CDATA[Nicolas Spinner]]></dc:creator>
            <pubDate>Wed, 27 Jan 2021 15:50:35 GMT</pubDate>
            <atom:updated>2021-01-27T15:50:35.416Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GMeGCU-FnXT-a8oTIJWM3Q.jpeg" /></figure><p>Even though SwiftUI is the new shiny tool to develop user interfaces for applications on all Apple platforms, UIKit will not be gone anytime soon. This also means, Interface Builder alongside Auto Layout will keep being the primary choice for many developers when designing user interfaces for iOS, macOS, tvOS and watchOS. This article mainly focuses on iOS, but most of it will generally be portable to the other aforementioned platforms.</p><h4>UIStackView to the rescue</h4><p>Since its introduction with the release of iOS 9 and especially in the last few years, UIStackView became my default choice when designing UI components in Interface Builder. To me, its main advantage is the fact that we can get rid of so many constraints which simplifies the layout extremely. I wrote about how I use UIStackView with Instagram‘s login screen as an example <a href="https://link.medium.com/dRDgCvUONcb">in my last article</a>.</p><p>In my experience I‘ve noticed that some developers decide to not use UIStackView when the screen design has a lot of different margins between the design elements. Since iOS 11, there’s an API that addresses exactly this issue, as Apple enhanced UIStackView with an instance method <a href="https://developer.apple.com/documentation/uikit/uistackview/2866023-setcustomspacing">setCustomSpacing</a>. The method accepts two parameters, the specific spacing value and the arranged subview after which the custom spacing should be applied. Unfortunately there’s no way to configure custom spacing in Interface Builder, which would improve discoverability for this kind of feature.</p><p>A huge benefit of UIStackView that I take advantage of a lot, is the fact that subviews that are being hidden, do not take up any space, just as setting the visibility of views to GONE in Android. This is especially useful when dealing with dynamic user interfaces which is extremely common since almost every app in 2021 deals with some kind of network requests.<br>There are some instances, though, where I would need a hidden arranged subview to not give up its space. The easiest fix, which sounds a little bit hacky, is to set the alpha value to 0, which to the user effectively looks like the view is being hidden.</p><p>I also love to use UIStackView within the layout of UITableViewCell or UICollectionViewCell instances. Since most of the time, I need the cells to determine their size themselves due to different text length, images, etc., configuring the layout with UIStackView helps achieving just that.</p><p>There has been a pretty significant change with the release of iOS 14. Previously Apple called UIStackView a non-rendering View, which was their way of saying the background color was being ignored. This changed with iOS 14, so if you accidentally set a background color on a UIStackView, this color is now being reflected in the UI.</p><h4>Sizes, widths and heights</h4><p>When designing custom views with Xibs, the default size that is shown in Interface Builder is the size of a specific device. In some cases however, we want to design small components, nowhere near the size of an entire screen. Sure, as I mentioned before, the actual size at runtime is being determined through constraints, but when designing smaller views, I prefer setting the Size in <em>Simulated Metrics</em> within the <em>Attributes Inspector</em> to <em>Freeform</em> and reduce the view‘s size in Interface Builder. With <em>Freeform</em>, you can basically set the view‘s size in Interface Builder to any size you want.</p><figure><img alt="In the Simulated Metrics menu, you can adjust the appearance of the view you are designing" src="https://cdn-images-1.medium.com/proxy/1*pEifB-J1Mo2_8ap9K7pACw.png" /><figcaption>In the Simulated Metrics menu, you can adjust the appearance of the view you are designing</figcaption></figure><p>Be careful when setting fixed font sizes in Interface Builder, as they will not react to the system text size the user has chosen. More on this topic with a real example can be found in the Accessibility part of <a href="https://link.medium.com/dRDgCvUONcb">my last article</a>.</p><h4>More smaller tips</h4><p>Due to the fact that localizable keys cannot be set in Interface Builder, I prefer only setting placeholder text like „Lorem Ipsum“ for text rendering views in Xibs or Storyboards and set the localizable key programmatically instead. This has the benefit at runtime, that it is immediately visible that a localizable key was not set in code, when you see a placeholder text.<br>You could also translate entire Xib files, which means you have the same Xib file for each language. I do not think this is particularly useful, especially in larger apps or apps which support many different languages, as you would need to maintain the same Xib file multiple times.</p><p>When connecting views from Interface Builder to your code, Xcode defaults to the IBOutlets being weak. That is pretty strange as they should actually be strong, which Apple also encourages. You can read more on that <a href="https://twitter.com/layoutsubviews/status/1030983794063421440?s=21">in this Twitter discussion</a>. When you control+drag a view into your code and change from weak to strong in the little overlay that pops up, Xcode will remember your setting.</p><figure><img alt="" src="https://cdn-images-1.medium.com/proxy/1*fln6PgVb4yrmIgOeua7LIg.png" /></figure><p>If you ever used UIScrollView in a Xib or Storyboard, chances are, Interface Builder always had something to complain. Since Xcode 11, I have less issues with that, as Apple made it possible to <a href="https://useyourloaf.com/blog/scroll-view-layouts-with-interface-builder/">use Content and Frame Layout Guides in Interface Builder</a>. But still, there are times when I do not want to set a specific size for my content and Interface Builder is complaining. This happens a lot, when I design the content of my UIScrollView and I set a <em>Greater Than or Equal</em> constraint to the bottom of the scroll view‘s <em>Content Layout Guide</em>.</p><figure><img alt="Interface Builder cannot determine the content height" src="https://cdn-images-1.medium.com/proxy/1*RgfCHG2LqiyleS4eL6FlAg.png" /><figcaption>Interface Builder cannot determine the content height</figcaption></figure><figure><img alt="These are the constraints that lead to Interface Builder complaining" src="https://cdn-images-1.medium.com/proxy/1*47hyyWzfa5u1f1K-EPympA.png" /><figcaption>These are the constraints that lead to Interface Builder complaining</figcaption></figure><p>At runtime, my constraints are perfectly fine, so I could just leave Interface Builder alone. I want to get rid of that red error icon, though, so there is a small trick to do that. I add a normal <em>Equal to</em> bottom constraint and remove said constraint at build time. This means, it will only be active at design time and Interface Builder will stop complaining.</p><figure><img alt="This checkbox leads to the constraint being removed at runtime" src="https://cdn-images-1.medium.com/proxy/1*v90GdqxanOJYRUJU29ABrA.png" /><figcaption>This checkbox leads to the constraint being removed at runtime</figcaption></figure><p>The last tip I want to share is simple: Just do not use <em>IBDesignables</em>. They were meant to make working with custom views in Interface Builder easier, but from my experience they seem broken and do not work reliably.</p><figure><img alt="IBDesignables crash a lot in Interface Builder" src="https://cdn-images-1.medium.com/proxy/1*I0bE8GUVjIDAmHeBcOABCg.png" /><figcaption>IBDesignables crash a lot in Interface Builder</figcaption></figure><p>It is pretty rare that they are getting rendered properly, which lead to me not using them anymore at all.</p><h4>Wrapping up</h4><p>I really like working with Interface Builder, but I think it takes some time to get used to it. Hopefully, I could point out some things you did not know about. What tips and tricks help you the most when working with Xibs or Storyboards? I would love to hear from you. You can reach out in the comments or contact me on <a href="https://twitter.com/nicolas_spinner">Twitter</a>.</p><p>Thanks for reading!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f72e63aa6e5c" width="1" height="1" alt=""><hr><p><a href="https://medium.com/stepup-development/interface-builder-tips-and-tricks-f72e63aa6e5c">Interface Builder tips and tricks</a> was originally published in <a href="https://medium.com/stepup-development">StepUp Development</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Write and maintain custom git commands]]></title>
            <link>https://medium.com/stepup-development/write-and-maintain-custom-git-commands-18f9077a7a0c?source=rss----1ece39e3c702---4</link>
            <guid isPermaLink="false">https://medium.com/p/18f9077a7a0c</guid>
            <category><![CDATA[git]]></category>
            <category><![CDATA[automation]]></category>
            <category><![CDATA[development]]></category>
            <category><![CDATA[developer]]></category>
            <dc:creator><![CDATA[Manuel Kunz]]></dc:creator>
            <pubDate>Fri, 15 Jan 2021 11:25:50 GMT</pubDate>
            <atom:updated>2021-01-15T11:25:49.951Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RpdCB8ssHVk6dpHL31rKbA.jpeg" /></figure><p>Git is a version control system used by many developers all around the globe. It has a lot of functionalities and you might, like me, not always remember everything you can do with it.</p><h3>Create a remote branch</h3><p>Let’s dive into it with an example that shows how to create and work with branches.</p><pre>$ git branch feature/stepup-blog</pre><p>The branch command will create a local branch only and not push it to the remote. This is pretty straightforward and the syntax is not very hard to remember.<br>But when I do create a new branch most of the times I want to switch to this branch immediately, so I have to checkout the branch I just created.</p><pre>$ git checkout feature/stepup-blog</pre><p>Next you are writing some code and it comes to the point you commit and push your changes. Remember, we are just working locally, so if you want to push your changes, git will tell you the following.</p><pre>fatal: The current branch feature/stepup-blog has no upstream branch.<br>To push the current branch and set the remote as upstream, use</pre><pre>git push --set-upstream origin feature/stepup-blog</pre><p>This basically speaks for itself, your branch has no upstream branch and it even tells you how to set this upstream branch.<br>Of course this is not the hardest git workflow that exists but it still annoys me after several times. What I like to do is create a little custom git command which execute these steps in one command, so I do not have to do all of these steps each time I create a new branch.</p><p>To maintain your git commands I suggest you create your own git repository where you can place your commands in. This enables you to track changes and share your custom commands.</p><p>I am using <a href="https://www.zsh.org/">zsh</a> so in my .zshrc file in my home directory, I added the following line to add the path to my repository to the PATH variable.</p><pre>PATH=&quot;$PATH:/Users/mkunz/Private/git-commands&quot;</pre><h3><strong>Now let’s create our first custom git command</strong></h3><p>We are going to create a command which creates a branch, switches to this branch and pushes the branch to the remote. <br>Later we want to use this command like any other git command and call it like this.</p><pre>$ git rbranch feature/stepup-blog</pre><p>In this case rbranch stands for remote branch but you can name the command whatever you like. So we need to create a file and make it executable. Make sure you are at the root directory of your repository and create the file git-&lt;commandName&gt;. I choose rbranch as the command name so I have to name the file git-rbranch.</p><pre>$ touch git-rbranch<br>$ chmod +x git-rbranch</pre><p>To find out if this works, open the file and add the following lines.</p><pre>#!/bin/bash<br>echo &quot;Hello world&quot;</pre><p>The first line tells Unix that the file is to be executed by /bin/bash. The following command will print <em>Hello World</em>.</p><p>Save it and now you can call your git command like the standard git commands you know.</p><pre>$ git rbranch<br>// Hello World</pre><p>This is all you need for your setup.</p><h3>Add functionality to the command</h3><p>And now let’s get back to the workflow we had in the beginning and create a new remote branch.<br>A git command is similar to a shell script so we can pass arguments to it and access these arguments.<br>So we just take the steps we have and place them in our command, the parameter can be accessed by <em>$1 </em>referring to the first parameter (<em>$0</em> points to the location of the command).</p><pre>#!/bin/bash</pre><pre>git branch $1<br>git checkout $1<br>git push --set-upstream origin $1</pre><p>And that is pretty much all you have to do.</p><p>I really like to make even simple workflows a little bit simpler. For example a <a href="https://github.com/KunzManuel/git-commands/blob/main/git-cleanup">git command that deletes local branches which are no longer available on the remote</a>. Maybe a git command which automatically adds your commit message to your changelog? <br>I have a <a href="https://github.com/KunzManuel/git-commands">repository</a> on GitHub with, at the moment, just a few git commands. <br>Feel free to contribute to the repository and share your custom git commands that you like to use. Also feel free to reach out to me on <a href="https://twitter.com/manuelkunz_sw">Twitter</a> if you have any feedback about this post.</p><p>Happy coding.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=18f9077a7a0c" width="1" height="1" alt=""><hr><p><a href="https://medium.com/stepup-development/write-and-maintain-custom-git-commands-18f9077a7a0c">Write and maintain custom git commands</a> was originally published in <a href="https://medium.com/stepup-development">StepUp Development</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Prototyping Instagram‘s login screen using UIStackView]]></title>
            <link>https://medium.com/stepup-development/prototyping-instagram-s-login-screen-using-uistackview-22307d81c866?source=rss----1ece39e3c702---4</link>
            <guid isPermaLink="false">https://medium.com/p/22307d81c866</guid>
            <category><![CDATA[uikit]]></category>
            <category><![CDATA[ios-development]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[instagram]]></category>
            <dc:creator><![CDATA[Nicolas Spinner]]></dc:creator>
            <pubDate>Wed, 30 Dec 2020 11:18:37 GMT</pubDate>
            <atom:updated>2020-12-30T11:18:36.947Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*OyiyP4ca4nkkBMvm64DbtQ.png" /></figure><p>There are many things to consider when implementing user interfaces in iOS. It starts with the basic UI, but there are many more things to think of, from dark mode to accessibility. In this blog post I want to show you my approach of implementing a design in iOS with Instagram‘s login screen as an example. If you want to follow along or have a look at my example project, you can check out the code on <a href="https://github.com/LoadingIndicator/InstagramExample">GitHub</a>. Notice that this article does not cover SwiftUI. I will follow up this article with another post about general tips and tricks when using Interface Builder.</p><h4>Analyzing the design</h4><p>Before I go to work in Xcode, I always analyze the given design and ask myself some questions. What elements does the screen consist of? Does it make sense to split up the screen into multiple UIViewControllers? How to display error states and loading animations?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/370/1*big_D4vfEmk6flQWmEuiYQ.png" /></figure><p>Looking at Instagram‘s login screen, the first thing that comes to my mind are the two text fields. This means, that we have to deal with a keyboard being shown, which means we might have to adjust the UI once the keyboard is visible. All the views in the center of the screen scream UIStackView to me as they all are aligned above each other. I think one UIViewController instance should be enough in this case, but you could also use a child view controller for the bottom part of the screen, if you would want to.</p><h4>Starting off in Interface Builder</h4><p>I generally prefer using Xibs over Storyboards to avoid merge conflicts. I am also not a huge fan of segues as it sort of hurts the flexibility of creating each screen from every part of the app. There is some UI code I am doing only programmatically, but generally I am way faster when designing the main components in Interface Builder. However this discussion should not be the topic of this article, enough posts have been written about this subject and I don‘t think what suits me best necessarily has to be the best workflow for other developers. For the login screen start off by dragging two UITextFields into Interface Builder and embed those in a UIStackView using the <em>Embed In</em> button on the bottom right. As the main part of the screen seems to be perfectly centered, constrain the UIStackView vertically and horizontally in the center of the view controller‘s view.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/561/1*7KQXkFz0W_zA5kDiQsroPA.png" /><figcaption>Centering the main UIStackView in Interface Builder</figcaption></figure><p>To make the stack view cover (almost) the entire width, it gets a leading and trailing constraint with a standard margin of 16.<br> Now the stack view just needs to be populated with all the subviews it needs. The UIStackView‘s Fill alignment and distribution are perfectly fine for this kind of design. The <em>Forgotten password?</em> button‘s alignment can be set to Right, every other view can just remain centered. For the separating view with the <em>OR</em> label, add another UIStackView containing two 1Pt high UIViews and the label in the center. This stack view needs to have the alignment set to Center, otherwise, the label would get truncated.<br> The Facebook SDK ships with a login button that can be used in your design, but as I did not want to include the sdk in my example project, the same can be done with a single UIButton. Setting the button‘s text <em>and</em> image lead to the desired design. Using this approach over e.g. another UIStackView with an image and a label, has the main benefit, that UIButton already has built in highlighting when the user interacts with it. To achieve the required margin between the button‘s image and text, just play around with the Image insets in the view‘s size inspector.<br> UIStackView enables us to use a bare minimum of constraints. We still need to make use of them sometimes, especially when we need views to have a specific width or height, e.g. the separator views in this example. You should also constrain the text field’s height to 45, since the default UITextField is a lot smaller. To match the text fields‘ height, the login button is being constrained to their height, which gives the UI a more consistent look.<br> With the main UI done, add another UIStackView for the bottom elements constraining it to the bottom of the screen as well as the leading and trailing edges using the same standard margin of 16. I decided to not include the separator above the elements inside the stack view as it should not have the margin and instead reach all the way to the screen‘s edges.</p><figure><img alt="Finished design in Interface Builder" src="https://cdn-images-1.medium.com/proxy/1*JfWz5U8Efdl7X9ipAb7qvw.jpeg" /><figcaption>Finished design in Interface Builder</figcaption></figure><p>Having done all that, our design already looks pretty good. But for some tweaks we now have to head over to the editor and write some code!</p><h4>Tweaking elements in code</h4><p>I wanted to challenge myself to make my design look as close to the original Instagram app as possible. There are four things that need to be done to improve the current prototype. First, the two text fields‘ placeholder‘s and text input need to have more padding than the default UITextField‘s insets. Therefore, we need to adjust the text fields‘ insets in code. Unfortunately adjusting a UITextField‘s insets is not as easy as one would imagine, the easiest way this can be done is through subclassing UITextField.</p><pre>class TextField: UITextField {<br><br>        let padding = UIEdgeInsets(top: 8, left: 12, bottom: 8, right: 12)<br>    <br>    override func textRect(forBounds bounds: CGRect) -&gt; CGRect {<br>        bounds.inset(by: padding)<br>    }<br><br>    override func placeholderRect(forBounds bounds: CGRect) -&gt; CGRect {<br>        bounds.inset(by: padding)<br>    }<br><br>    override func editingRect(forBounds bounds: CGRect) -&gt; CGRect {<br>        bounds.inset(by: padding)<br>    }<br><br>}</pre><p>Changing the two text fields‘ class names in the Identity Inspector of Interface Builder now leads to the increased padding at runtime.<br> The second thing that needs to be done in code, is changing the text fields‘ placeholder text color which is not the default one in the Instagram app. This can be done by setting an attributedPlaceholder, which can be extracted into a private extension.</p><pre>private extension UITextField {<br><br>    func setPlaceholder(_ placeholder: String) {<br>        attributedPlaceholder = NSAttributedString(string: placeholder,<br>                attributes: [NSAttributedString.Key.foregroundColor: UIColor.systemGray])<br>    }<br><br>}</pre><p>After that, just call this method on the two text fields with their placeholder texts.</p><pre>usernameTextField.setPlaceholder(&quot;Phone number, username or email address&quot;)<br>passwordTextField.setPlaceholder(&quot;Password&quot;)</pre><p>In a real project you would of course move those strings into a Localizable.strings file. But sure you were already aware of that, weren‘t you? ;)</p><p>The third thing that is still missing, are the rounded corners of the login button. There actually is a way to do this with user defined runtime attributes in the Size Inspector of Interface Builder. But I prefer doing this in code since that is much easier to maintain and discover. In order for the button to be adjusted in code, it first needs to be connected as an IBOutlet. If you‘re following along this article in Xcode, you can already connect all the other views as well, you are going to need those later in the accessibility section.<br> Once the button has been connected, its corners can be rounded by adjusting the corner radius.</p><pre>loginButton.layer.cornerRadius = 5</pre><p>You might guess the last thing that needs to be done to match the original design as closely as possible when you look at the margins between the individual views. The current spacing that is being set for the central stack view does not represent the margins in the original design. This means, we need to make use of UIStackView‘s <a href="https://developer.apple.com/documentation/uikit/uistackview/2866023-setcustomspacing">setCustomSpacing</a> method, that enables us to adjust the standard spacing after a specific view. Having configured the default spacing to 16, requires an alternate spacing after four subviews. Calling this method in viewDidLoad now leads to a pretty close result at runtime resembling the actual Instagram app.</p><pre>private func setupCustomSpacing(keyboardVisible: Bool) {<br>    mainStackView.setCustomSpacing(keyboardVisible ? 8 : 12, after: passwordTextField)<br>    mainStackView.setCustomSpacing(keyboardVisible ? 16 : 24, after: forgotPasswordButton)<br>    mainStackView.setCustomSpacing(keyboardVisible ? 16 : 24, after: loginButton)<br>    mainStackView.setCustomSpacing(keyboardVisible ? 16 : 40, after: separatorStackView)<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/369/1*WjO9Js1YRT2LxFKed09dOQ.png" /><figcaption>Running the app now shows the adjusted spacing between the individual elements</figcaption></figure><p>With the app looking nice at runtime, you might be tempted to call it a day and consider the design being fully implemented. However, there is still a lot of things that can be improved that are not obvious at first. So let‘s start finetuning by supporting dark mode!</p><h4>Supporting dark mode</h4><p>Implementing dark mode support is pretty easy in this example. There are actually only two things that need to be adjusted. First, the Instagram logo has to get a white counterpart that is going to be displayed in dark mode. Selecting the Instagram logo in Assets.xcassets, just change the <em>Appearances</em> dropdown from <em>None</em> to <em>Any, Dark</em>, which enables us to add a different image for its dark appearance.</p><figure><img alt="Supporting dark mode for icons is really convenient" src="https://cdn-images-1.medium.com/proxy/1*T1lrFDrrJH1HOzZBipb-hQ.jpeg" /><figcaption>Supporting dark mode for icons is really convenient</figcaption></figure><p>To already finish supporting dark mode, just add a color asset in Asssets.xcassets for the background color of the two text fields. Adding two different colors for light and dark mode works the same as the way it works with icons. Changing the <em>Appearances</em> to <em>Any, Dark</em> enables us to set a light background (#F9F9F9) for light mode and a dark background (#111211) for dark mode. That‘s it! Since the screen‘s background was already automatically set to <em>System Background</em> in Interface Builder, the app looks great in dark mode. Debugging dark mode is especially easy since it can simply be toggled in the <em>Environment Overrides</em> menu in Xcode alongside other options for <em>Text</em> and <em>Accessibility</em>.</p><figure><img alt="Toggling dark mode can be done in the ‌Environment Overrides menu" src="https://cdn-images-1.medium.com/proxy/1*kzEXlT1arBPoqsxjyovQjw.jpeg" /><figcaption>Toggling dark mode can be done in the <em>‌Environment Overrides</em> menu</figcaption></figure><h4>React to the keyboard being shown</h4><p>When analyzing the design earlier, I mentioned potentially adjusting the UI when the keyboard is being shown. Since the main elements are centered on screen, this is unavoidable here to prevent key elements from being hidden by the keyboard.<br> In this case, the most logical way to address this problem, would be through constraining the main stack view to the top of the screen instead of centering it once the keyboard is being shown. So let‘s head back to the editor and start listening to the system‘s notification for the keyboard being shown or hidden.</p><pre>NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow),<br>        name: UIResponder.keyboardWillShowNotification, object: nil)<br>NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide),<br>        name: UIResponder.keyboardWillHideNotification, object: nil)</pre><p>Additionally, the main stack view‘s centerY constraint needs to be connected as an IBOutlet, which enables us to activate or deactivate when needed. Moreover, add another private variable mainStackViewTopConstraint which is being initialized in viewDidLoad but not yet activated.</p><pre>mainStackViewTopConstraint = mainStackView.topAnchor.constraint(<br>            equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16<br>        )</pre><p>With this preparation being done, we can finally implement keyboardWillShow and keyboardWillHide which we defined as the selectors being called once the system’s notifications are fired.</p><pre>@objc private func keyboardWillShow() {<br>    mainStackViewCenterYConstraint.isActive = false<br>    mainStackViewTopConstraint.isActive = true<br>    UIView.animate(withDuration: 0.3) { [weak self] in<br>        self?.view.layoutIfNeeded()<br>    }<br>}<br><br>@objc private func keyboardWillHide() {<br>    mainStackViewCenterYConstraint.isActive = true<br>    mainStackViewTopConstraint.isActive = false<br>    UIView.animate(withDuration: 0.3) { [weak self] in<br>        self?.view.layoutIfNeeded()<br>    }<br>}</pre><p>We just need to toggle the constraint‘s isActive property and call layoutIfNeeded() in an animation block to make the stack view being animated to the top nicely. To further improve the two methods, consider adjusting the setupCustomSpacing method with a keyboardVisible parameter to decrease the stack view’s custom spacing we configured before for the time the keyboard is being shown.</p><pre>private func setupCustomSpacing(keyboardVisible: Bool) {<br>        mainStackView.setCustomSpacing(keyboardVisible ? 8 : 12, after: passwordTextField)<br>        mainStackView.setCustomSpacing(keyboardVisible ? 16 : 24, after: forgotPasswordButton)<br>        mainStackView.setCustomSpacing(keyboardVisible ? 16 : 24, after: loginButton)<br>        mainStackView.setCustomSpacing(keyboardVisible ? 16 : 40, after: separatorStackView)<br>    }</pre><p>To further improve the user experience, add another method which hides the keyboard when the user taps somewhere else on the screen.</p><pre>private func setupDismissKeyboardGesture() {<br>    let tapGestureRecognizer = UITapGestureRecognizer(target: self,<br>            action: #selector(dismissKeyboard))<br>    view.addGestureRecognizer(tapGestureRecognizer)<br>}<br><br>@objc private func dismissKeyboard() {<br>    view.endEditing(true)<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*7uWkVFxlvNAuxpJQdT8K4w.gif" /><figcaption>Now the content is being animated to the top as soon as the keyboard is being shown</figcaption></figure><p>You could also consider hiding the Instagram logo once the keyboard is visible to make sure the UI looks good on smaller devices. The actual Instagram app does not hide the logo when moving the elements to the top, but I think it could make sense, so I included it in my example project.</p><h4>Improving accessibility using Dynamic Type</h4><p>The last thing I want to showcase in this article, is a thing you should always keep in mind when designing and implementing user interfaces: Accessibility. Of course this topic is way too big to cover in one article. But at least I want to show you how you can support <em>Dynamic Type</em> in this prototype. Supporting <em>Dynamic Type</em> means that your app‘s labels, buttons, etc. react to the system text size being customized. Many older users tend to adjust the system font to a larger size since their visibility is often impaired. But there are also some folks that use a smaller size than the default font to fit more content on screen.<br> With <em>Dynamic Type</em> you can respect the user‘s preference also in your app. The easiest way to support it is being done in using the standard Text Styles Apple has defined, e.g. <em>Body</em>, <em>Caption 1</em>, <em>Headline</em> or <em>Large Title</em>. They adjust their size automatically and you do not have to do anything else.<br> However, often those predefined text styles are not enough to implement a specific design. Consequently, we often just set a custom text size, which we can also do in Interface Builder. Those custom text sizes set in Interface Builder do <em>not</em> react to the system settings the user configured, though.<br> Therefore, this needs to be done in code. I like to use a simple extension on UIFont when setting the font of a specific view.</p><pre>extension UIFont {<br><br>    static func scaledSystemFont(size: CGFloat, weight: UIFont.Weight = .regular) -&gt; UIFont {<br>        .systemFont(ofSize: UIFontMetrics.default.scaledValue(for: size), weight: weight)<br>    }<br><br>}</pre><p>Having added the extension, we can set the scaled font for all text displaying views, e.g. the login button.</p><pre>loginButton.titleLabel?.font = .scaledSystemFont(size: 15, weight: .medium)</pre><p>To prevent the text of some labels or buttons from being truncated, when the user has a really large text size configured, the adjustsFontSizeToFitWidth property can be set to true. This setting shrinks the font size in case it cannot fit the entire text in the available space.</p><pre>facebookButton.titleLabel?.adjustsFontSizeToFitWidth = true</pre><p>After setting the font size for all labels, buttons and text fields in code, the UI now respects the system settings by the user.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/369/1*V91ESuVggKR6KMpkeV-ZoA.png" /><figcaption>Configuring a large system font size is now being reflected in our prototype</figcaption></figure><h4>Points of improvement and conclusion</h4><p>There are many more things than just the actual design when implementing a user interface, which I wanted to show you with this prototype. Accessibility, dark mode and reacting to the keyboard being displayed, are just a few examples to consider.</p><p>If you would like to further improve the prototype, feel free to do so, here are just a few areas you could work on: The prototype currently does not have the eye icon on the password text field to toggle the password visibility. You could also react to the done button being tapped when a text field is being focused, and jump to the next one or trigger the actual login process. While this process is running, the original Instagram app shows a UIActivityIndicatorView on the login button, to make the process visible to the user. Finally, you could dig into <em>Combine</em> and disable the login button until a username and password have been entered by the user.<br> How do you approach implementing a UI design in iOS? I am curious to find out whether you have different techniques or workflows. You can reach out in the comments or contact me on <a href="https://twitter.com/nicolas_spinner">Twitter</a>.</p><p>Thanks for reading!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=22307d81c866" width="1" height="1" alt=""><hr><p><a href="https://medium.com/stepup-development/prototyping-instagram-s-login-screen-using-uistackview-22307d81c866">Prototyping Instagram‘s login screen using UIStackView</a> was originally published in <a href="https://medium.com/stepup-development">StepUp Development</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Simplify your developer life with Boop]]></title>
            <link>https://medium.com/stepup-development/simplify-your-developer-life-with-boop-99356b2e8a2a?source=rss----1ece39e3c702---4</link>
            <guid isPermaLink="false">https://medium.com/p/99356b2e8a2a</guid>
            <category><![CDATA[android]]></category>
            <category><![CDATA[ios-localization]]></category>
            <category><![CDATA[automation]]></category>
            <category><![CDATA[android-resources]]></category>
            <category><![CDATA[ios]]></category>
            <dc:creator><![CDATA[Manuel Kunz]]></dc:creator>
            <pubDate>Sun, 30 Aug 2020 11:48:38 GMT</pubDate>
            <atom:updated>2020-08-30T11:48:38.751Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*31otc8SYcU_62ljvcddsmw.jpeg" /></figure><pre>&quot;main_hero_name&quot; = &quot;Hercules&quot;;<br>&lt;string name=&quot;main_hero_name&quot;&gt;Hercules&lt;/string&gt;</pre><p>If you have ever seen this, you are probably an app developer, you have at least developed for Android and iOS and at some point you dealt with assets like localizables.</p><p>Keeping your iOS and Android localizables in sync can be hard, especially when you work in a team and your project grows. Your localizables end up being different on both platforms and it just keeps getting harder to maintain. It would be simple if you could just copy and paste those strings from one platform to the other and you could be sure that every key and value is the same. One way to do this is to use tools other people have already written for us. One of these tools is <a href="https://boop.okat.best/"><strong>Boop</strong></a>.</p><p>You can use <a href="https://boop.okat.best/"><strong>Boop</strong></a> for your daily routine of developing, e.g. format and validate your JSON file, decode and encode data, count words, sort lines and so much more. With Boop you can use one offline tool instead of 5 online tools you have to keep switching between to get your results. The best part is, you can <a href="https://github.com/IvanMathy/Boop/blob/main/Boop/Documentation/CustomScripts.md">add custom scripts</a> easily.</p><p>For the Localizable handling between iOS and Android you could use two simple scripts to convert Android to iOS Localizables and conversely.</p><h3>Android to iOS</h3><pre>/**<br>  {<br>    &quot;api&quot;:1,<br>    &quot;name&quot;:&quot;Android Strings to iOS Localizables&quot;,<br>    &quot;description&quot;:&quot;Converts Android Strings to iOS Localizables&quot;,<br>    &quot;author&quot;:&quot;Manuel Kunz&quot;,<br>    &quot;icon&quot;:&quot;quote&quot;,<br>    &quot;tags&quot;:&quot;string, android, ios&quot;<br>  }<br>**/<br><br>function main(input) {<br>    let lines = input.fullText.split(&#39;\n&#39;)<br>    var result = []<br>    lines.forEach(element =&gt; {<br>      var temp = element<br>      temp = temp.replace(&quot;&lt;string name=&quot;, &quot;&quot;)<br>      temp = temp.replace(&quot;&lt;/string&gt;&quot;, &quot;\&quot;;&quot;)<br>      temp = temp.replace(&quot;&gt;&quot;, &quot; = \&quot;&quot;)<br>      result.push(temp)      <br>    })<br>  <br>    input.fullText = result.join(&#39;\n&#39;)<br>}</pre><h3>iOS to Android</h3><pre>/**<br>  {<br>    &quot;api&quot;:1,<br>    &quot;name&quot;:&quot;iOS Localizables to Android Strings&quot;,<br>    &quot;description&quot;:&quot;Converts iOS Localizables to Android Strings&quot;,<br>    &quot;author&quot;:&quot;Manuel Kunz&quot;,<br>    &quot;icon&quot;:&quot;quote&quot;,<br>    &quot;tags&quot;:&quot;string, android, ios&quot;<br>  }<br>**/<br><br>function main(input) {<br>    let lines = input.fullText.split(&#39;\n&#39;)<br>    var result = []<br>    lines.forEach(element =&gt; {<br>      if(element !== &quot;&quot;) {<br>        var regex = /&quot;(.*?)&quot;/g<br>        var matches = [];<br>        var match = regex.exec(element);<br>        while (match != null) {<br>            matches.push(match[1]);<br>            match = regex.exec(element);<br>        }<br>        result.push(&quot;&lt;string name=\&quot;&quot; + matches[0] + &quot;\&quot;&gt;&quot; + matches[1] + &quot;&lt;/string&gt;&quot;)<br>      }      <br>    })   <br>    input.fullText = result.join(&#39;\n&#39;)<br>}</pre><p>Tools like this just help you get your work done in one place and make your developer life easier. If you have any ideas of scripts you could imagine to write and include to <a href="https://boop.okat.best/"><strong>Boop</strong></a>, feel free to share your ideas. We would like to hear from you.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=99356b2e8a2a" width="1" height="1" alt=""><hr><p><a href="https://medium.com/stepup-development/simplify-your-developer-life-with-boop-99356b2e8a2a">Simplify your developer life with Boop</a> was originally published in <a href="https://medium.com/stepup-development">StepUp Development</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Handling multiple sheets in SwiftUI]]></title>
            <link>https://medium.com/stepup-development/handling-multiple-sheets-in-swiftui-2e9a73d99cd7?source=rss----1ece39e3c702---4</link>
            <guid isPermaLink="false">https://medium.com/p/2e9a73d99cd7</guid>
            <category><![CDATA[development]]></category>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[swift]]></category>
            <dc:creator><![CDATA[Manuel Kunz]]></dc:creator>
            <pubDate>Wed, 10 Jun 2020 17:14:37 GMT</pubDate>
            <atom:updated>2020-06-10T17:14:37.632Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HHpvjr0k_DW7Y9WzGSv0wg.jpeg" /></figure><p>In order for a view to be displayed modally in SwiftUI, the sheet modifier can be used. In its simplest form a sheet is being presented if a given condition is true.</p><pre>struct ContentView: View {</pre><pre>@State var presentSheet = false</pre><pre>var body: some View {<br>        VStack {<br>            Button(&quot;Show Settings&quot;) {<br>                self.presentSheet = true<br>            }<br>        }.sheet(isPresented: $presentSheet) {<br>            SettingsView()<br>        }<br>    }<br>}</pre><p>In this example, the sheet modifier is applied to the VStack. However, it could also be applied to the Button directly.</p><p>What if we wanted to display more than one sheet from a specific view, though?</p><p>The most logical choice would be to just place another sheet modifier below the existing one, since it is possible to chain modifiers as we please. Unfortunately this does not work. In order for us to be able to use multiple sheets, each modifier has to be applied to the button directly. Using two or more consecutive sheet modifiers does not work in SwiftUI, only the last sheet modifier will be applied and can be used. As a result, we could design our view like this.</p><pre>@State var showSettingsView = false<br>@State var showProfileView = false</pre><pre>var body: some View {<br>    VStack {<br>        Button(&quot;Show Settings&quot;) {<br>            self.showSettingsView = true<br>        }.sheet(isPresented: $showSettingsView) {<br>            SettingsView()<br>        }<br>        Button(&quot;Show Profile&quot;) {<br>            self.showProfileView = true<br>        }.sheet(isPresented: $showProfileView) {<br>            ProfileView()<br>        }<br>    }<br>}</pre><p>But as you can imagine in a working app, this view won’t be as compact. If you need to handle multiple different sheets, the code gets blown up very quickly. Additionally, each sheet needs its own @<em>State</em> to be presented.</p><p>To avoid <em>massive Views</em>, it would be great if there was a way to separate this navigation logic from our view into a dedicated file. There are different alternatives that simplify the handling of multiple sheet modifiers. This article shows one of them.</p><p><strong>Moving the presentation logic into an observable ViewModel</strong></p><p>First of all we are going to separate the logic of which sheet will be shown to a separate view model.</p><pre>class SheetNavigator: ObservableObject {<br>    @Published var showSettingsView = false<br>    @Published var showProfileView = false<br>}</pre><p>This <em>SheetNavigator</em> class has to conform to the <em>ObservableObject</em> protocol and will be observed by the view itself using the <em>@ObservedObject</em> property wrapper.</p><pre>@ObservedObject var sheetNavigator = SheetNavigator()<br>var body: some View {<br>    VStack {<br>        Button(&quot;Show Settings&quot;) {<br>            self.sheetNavigator.showSettingsView = true<br>        }.sheet(isPresented: self.$sheetNavigator.showSettingsView) {<br>      SettingsView()<br>        }<br>        Button(&quot;Show Profile&quot;) {<br>            self.sheetNavigator.showProfileView = true<br>        }.sheet(isPresented: self.$sheetNavigator.showProfileView) {<br>            ProfileView()<br>        }<br>    }<br>}</pre><p>After we moved our presentation state variables into the view model, the code did not really improve. The state handling is not scalable and we still have multiple sheet modifiers cluttering our view. Let‘s reduce those at first to just one sheet modifier.</p><pre>VStack {<br>    Button(&quot;Show Settings&quot;) {<br>        self.sheetNavigator.showSettingsView = true<br>    }<br>    Button(&quot;Show Profile&quot;) {<br>        self.sheetNavigator.showProfileView = true<br>    }<br>}.sheet(isPresented: ???) {<br>    ???<br>}</pre><p>At this point we only have one sheet modifier, but which view should be shown and to which state variable should we bind the sheet? The answer is our SheetNavigator.</p><pre>class SheetNavigator: ObservableObject {<br>    var showSettingsView = false<br>    var showProfileView = false<br>    @Published var showSheet = false<br>    <br>    func sheetView() -&gt; AnyView {<br>        if showFirstView {<br>            return Text(&quot;SettingsView&quot;).eraseToAnyView()<br>        } else if showSecondView {<br>            return Text(&quot;ProfileView&quot;).eraseToAnyView()<br>        }<br>        return Text(&quot;Empty&quot;).eraseToAnyView()        <br>    }<br>}</pre><pre>extension View {<br>    func eraseToAnyView() -&gt; AnyView {<br>        AnyView(self)<br>    }<br>}</pre><p>We add an additional bool state to handle the binding for all sheets. The function <em>sheetView</em> returns the View we want to show for the selected button. All this logic happens in the navigator and our view is way simpler than before.</p><p>Since we are dealing with different views that are displayed in our sheets, we have to type erase said views to AnyView. For the sake of readability, we added a little View extension to simplify wrapping a view inside of AnyView.</p><p>As there might be performance implications when using AnyView, you could also consider wrapping your views inside a <em>Group</em>.</p><pre>VStack {<br>    Button(&quot;Show Settings&quot;) {<br>        self.sheetNavigator.showSettingsView = true<br>        self.sheetNavigator.showSheet = true<br>    }<br>    Button(&quot;Show Profile&quot;) {<br>        self.sheetNavigator.showProfileView = true<br>        self.sheetNavigator.showSheet = true<br>    }    <br>}.sheet(isPresented: self.$sheetNavigator.showSheet) {<br>    self.sheetNavigator.sheetView()<br>}</pre><p>But it’s not perfect, since we need to set two different booleans in the action of a button, one for the sheet that should be shown and one for the state that is bound to the sheet modifier. Additionally, the amount of state variables required, will grow for each new sheet we want to show. Let‘s use a different approach, that is more scalable and also improves readability: Enums.</p><p><strong>Enums to the rescue!</strong></p><p>Now there is only one published parameter left, the <em>showSheet</em> state that is bound to the sheet modifier. For the different sheet views we want to present, we added a <em>SheetDestination</em> enum.</p><pre>class SheetNavigator: ObservableObject {</pre><pre>@Published var showSheet = false<br>    var sheetDestination: SheetDestination = .none<br>    <br>    enum SheetDestination {<br>        case none<br>        case settings<br>        case profile<br>    }<br>    <br>  func sheetView() -&gt; AnyView {<br>      switch sheetDestination {<br>      case .none:<br>          return Text(&quot;None&quot;).eraseToAnyView()<br>      case .settings:<br>          return SettingsView().eraseToAnyView()<br>      case .profile:<br>          return ProfileView().eraseToAnyView()<br>      }<br>  }<br>}</pre><p>With these changes the View will now look like this.</p><pre>@ObservedObject var sheetNavigator = SheetNavigator()<br>var body: some View {<br>    VStack {<br>        Button(&quot;Show Settings&quot;) {<br>            self.sheetNavigator.sheetDestination = .settings<br>            self.sheetNavigator.showSheet = true<br>        }<br>        Button(&quot;Show Profile&quot;) {<br>            self.sheetNavigator.sheetDestination = .profile<br>            self.sheetNavigator.showSheet = true<br>        }<br>    }.sheet(isPresented: self.$sheetNavigator.showSheet) {<br>        self.sheetNavigator.sheetView()<br>    }<br>}</pre><p>At this point, we are still calling <em>showSheet</em> ourselves. Actually, setting the <em>sheetDestination</em> should really be enough. So we remove setting <em>showSheet</em> to true from the view and handle this with a property observer.</p><pre>var sheetDestination: SheetDestination = .none {<br>    didSet {<br>        showSheet = true<br>    }<br>}</pre><p>Every time the <em>sheetDestination</em> is being set, we set <em>showModal</em> to true.</p><pre>VStack {<br>    Button(&quot;Show Settings&quot;) {<br>        self.sheetNavigator.sheetDestination = .settings<br>    }<br>    Button(&quot;Show Profile&quot;) {<br>        self.sheetNavigator.sheetDestination = .profile<br>    }<br>}.sheet(isPresented: self.$sheetNavigator.showSheet) {<br>    self.sheetNavigator.sheetView()<br>}</pre><p><strong>What‘s next?</strong></p><p>In using our navigator, we managed to condense our view and moved most of our navigation logic out of it. But there is still room for improvement. We can take advantage of the fact that Swift enums support associated values and pass additional data to the presented sheet views.</p><p>For instance, we can make the profile enum case accept a String as parameter, if we want to pass the name of the user to the profile view.</p><pre>enum SheetDestination {<br>    case none<br>    case settings<br>    case profile(name: String)<br>}</pre><p>When setting the <em>sheetDestination</em> to the <em>profile</em> case, our call site would look like this.</p><pre>Button(&quot;Show Profile&quot;) {<br>   self.sheetNavigator.sheetDestination = .profile(name: &quot;Christoph&quot;)<br>}</pre><p>The profile case of the switch statement within the <em>sheetView</em> function, has to be adjusted as well. Here, we can use the given parameter and pass it to the ProfileView.</p><pre>case .profile(name: let userName):<br>         return ProfileView(name: userName).eraseToAnyView()</pre><p>Keep in mind to avoid too many destinations in one Navigator class. In fact, each view you are using should have its own Navigator.</p><p>To establish a consistent style, you could use a Navigator Protocol each Navigator can confirm to.</p><pre>protocol NavigatorProtocol: ObservableObject {<br>    associatedtype SheetDestination<br>    func sheetView() -&gt; AnyView<br>}</pre><p>I hope you enjoyed this little concept of handling multiple sheets. How do you handle navigating to different views in SwiftUI? Do you have similar approaches? Let us know! We‘d love to hear your ideas and hope this article helped to StepUp your developer game.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2e9a73d99cd7" width="1" height="1" alt=""><hr><p><a href="https://medium.com/stepup-development/handling-multiple-sheets-in-swiftui-2e9a73d99cd7">Handling multiple sheets in SwiftUI</a> was originally published in <a href="https://medium.com/stepup-development">StepUp Development</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Building iOS frameworks with bitrise]]></title>
            <link>https://medium.com/stepup-development/building-ios-frameworks-with-bitrise-4d4fd2bc123f?source=rss----1ece39e3c702---4</link>
            <guid isPermaLink="false">https://medium.com/p/4d4fd2bc123f</guid>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[framework]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[bitrise]]></category>
            <dc:creator><![CDATA[Nicolas Spinner]]></dc:creator>
            <pubDate>Mon, 08 Jun 2020 13:16:43 GMT</pubDate>
            <atom:updated>2020-06-08T13:16:43.586Z</atom:updated>
            <content:encoded><![CDATA[<h3>Building iOS frameworks with Bitrise</h3><figure><img alt="Space Image" src="https://cdn-images-1.medium.com/max/1024/1*QCFDDzRri5TbYnRWefOV9w.jpeg" /></figure><p>Bitrise is a really nice Continuous Integration service with a great user interface. Setting up a CI build for an iOS app is a matter of minutes. However, if you want to use Bitrise to build an iOS framework, it gets a bit trickier. Ideally, we’d want Bitrise to build our framework and — in the case of success — attach the built framework as an artifact.</p><h4>Setting up a custom script</h4><p>Since there is no pre configured workflow step for building a framework, we need to do this in form of a custom script. What helped us in the first place, was <a href="https://discuss.bitrise.io/t/building-ios-frameworks-using-bitrise/3987">a thread on the Bitrise forums</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/proxy/1*rNxE922yfnoTeNbet9_piQ.png" /></figure><p>The script has to be placed into the <em>Script content</em> window of the workflow editor and consists of two parts. In the first step, we want to create our framework twice, once for the simulator architecture x86 and once for the arm architecture which is required for the framework to run on a real device.</p><pre>xcodebuild -project FrameworkName.xcodeproj -scheme SchemeName -configuration Release -derivedDataPath $BITRISE_DEPLOY_DIR/iphoneos -sdk iphoneos clean build xcodebuild -project FrameworkName/FrameworkName.xcodeproj -scheme SchemeName -configuration Release -derivedDataPath $BITRISE_DEPLOY_DIR/iphonesimulator -sdk iphonesimulator clean build</pre><p>Notice that the script runs from the root directory of your git repository. Hence, if your Xcode project is not located in the root directory, you need to consider this for the project command.</p><h4>Piece the frameworks together</h4><p>Now that we have written our script for creating the framework for each architecture, we can piece them together, so we have one final framework. In the past, we would have used the lipo terminal command to bundle the two frameworks together into one so called <em>fat lib</em>. However, with the introduction of Xcode 11, there is a new way to package frameworks, called XCFramework, which has several advantages. For instance, we no longer need to slice the simulator architecture from our fat lib when uploading our apps to the AppStore. You can find more info on XCFrameworks in general in <a href="https://medium.com/trueengineering/xcode-and-xcframeworks-new-format-of-packing-frameworks-ca15db2381d3">this article</a>.</p><h4>Creating the XCFramework</h4><p>As you would expect, with the introduction of XCFrameworks, Apple also enhanced the xcodebuild command line tool. We can use the -create-xcframework command to finalize our custom script. To improve the structure of the deploy directory, we first create a new directory, which will contain the final .xcframework file.</p><pre>mkdir $BITRISE_DEPLOY_DIR/xcframework xcodebuild -create-xcframework -framework $BITRISE_DEPLOY_DIR/iphoneos/Build/Products/Release-iphoneos/FrameworkName.framework -framework $BITRISE_DEPLOY_DIR/iphonesimulator/Build/Products/Release-iphonesimulator/FrameworkName.framework -output $BITRISE_DEPLOY_DIR/xcframework/FrameworkName.xcframework</pre><h4>Final steps</h4><p>Now that we finished our custom script, there are just a couple of smaller things we need to do. In the workflow editor, we need to have a <em>Deploy to bitrise</em> workflow step after our custom script step. It is important to set the <em>Compress the artifacts into one file</em> option to true, which makes sure, that the configured <em>Deploy directory or file path</em> (also in the <em>Deploy to bitrise</em> step) <em>and</em> all its subdirectories are deployed.</p><p>By default, <a href="https://devcenter.bitrise.io/tips-and-tricks/attach-any-file-to-build/">it does not deploy files recursively</a>. Lastly, in order for an XCFramework to be built, we have to specify the <em>Build libraries for Distribution</em> build setting within our Xcode project to Yes. That’s it! Each of our Bitrise build now has a .zip file attached, containing our created XCFramework.</p><figure><img alt="" src="https://cdn-images-1.medium.com/proxy/1*4mKERn0oEcKp2skl_LWBcQ.jpeg" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/proxy/1*YZZo9kRewOzjeA2F4_LSDQ.jpeg" /></figure><p>Have you used custom scripts in Bitrise? If yes, we’re curious to find out which purpose you used it for. Thanks for reading this article!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4d4fd2bc123f" width="1" height="1" alt=""><hr><p><a href="https://medium.com/stepup-development/building-ios-frameworks-with-bitrise-4d4fd2bc123f">Building iOS frameworks with bitrise</a> was originally published in <a href="https://medium.com/stepup-development">StepUp Development</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>