Showing posts with label Swift. Show all posts
Showing posts with label Swift. Show all posts

Saturday, May 06, 2023

SwiftUI Challenge Edutainment Multiplication Game

 This is going to show the development of my solution to the Edutainment challenge.


First I used a VStack and HStack to create a table layout.



Based on position I began to change the attributes of the Text.


If an entry in the table is "masked" I change the output.


Then I changed the Text entries in the table to be Buttons and added a background image.


For now every time the "Mask" button is pressed I randomly mask out a value. I change the mask from "*" to "??" and change the color and size on the masked value.


When you tap a masked entry a question is displayed in an alert.


If you enter the correct value another alert acknowledges and the mask is taken off the entry in the table.


5 x 2 is no longer masked because of the correct answer.



To get this to work I had to learn how to use an ObservableObject.

The next thing was to select the multipliers. This is done by tapping a row or column header.
This allows the user to select what they want to practice. Here the user has selected column "3" and row "10". The values are masked for 3 and 10 for both the rows and the columns. This is because 3 x 5 is the same as 5 x 3.




Next I begin the View that will allow the user to play the game.







Next I created a new UIView and used @Binding to share the needed data. For now I just rough out the random generation of questions.

The way it works is that the user taps a row or column header. The row or column header is currently blue when selected. Then a set of random questions are generated. I put them in a list for now. I am not sure how the UI will end up.





After running into bugs where all the Buttons in a Form view perform their action when any button is selected and the annoyance that an array in an Observable object doesn't update I have finally got to my final solution.


Changing buttons and such...



Getting an array of responses by using an Observable Object.


Changing the colors.



Click a row or column label to select which number you want to practice.
Click Play and it will mask out the answers for how many questions you want.



Click a masked entry and the app prompts you.








The app keeps a list of your answers. If your answer is incorrect the value remains masked in the multiplication table.





I probably over did the assignment but I wanted to dig deeper.



















Wednesday, April 19, 2023

Swift Functions as Parameters and Trailing Closure

Place the following code in an XCode Play Ground:


import UIKit


struct Person {

    let birthYear : Int

    let name : String

    let tag : String

}


//The build function allows a builder function to be passed in. That function

//can create a new person based on the paramaters passed in

extension Person {

    static func build(birthYear: Int, name: String, tag: String, builder: (_ birthYear: Int, _ name : String, _ tag: String) -> Person) -> Person {

        

        return builder(birthYear, name, tag)

    }

}


//Functions can have argument labels and parameter names


//Argument labels and Parameter names

func personBuilderOne(theBirthYear birthYear: Int, theName name: String, theTag tag: String) -> Person {

    return Person(birthYear: birthYear, name: name, tag: tag + " Builder One")

}


//No Argument labels, just parameter names

func personBuilderTwo(birthYear: Int, name: String, tag: String) -> Person {

    return Person(birthYear: birthYear, name: name, tag: tag + " Builder Two")

}


//Parameter names are not needed when calling

func personBuilderThree(_ birthYear: Int, _ name: String, _ tag: String) -> Person {

    return Person(birthYear: birthYear, name: name, tag: tag + " Builder Three")

}


//----------------------------------------------------------

//Notice how the signature changes for the function paramter

var person = Person.build(birthYear: 1900, name: "John", tag: "tag", builder: personBuilderOne(theBirthYear:theName:theTag:))

print(person)


person = Person.build(birthYear: 1900, name: "John", tag: "tag", builder: personBuilderTwo(birthYear:name:tag:))

print(person)


person = Person.build(birthYear: 1900, name: "John", tag: "tag", builder: personBuilderThree(_:_:_:))

print(person)

//----------------------------------------------------------



//The builder can be done by using a closure

//The closure params are contrived just to show how they are used

person = Person.build(birthYear: 1910, name: "Mary", tag: "tag") { bYear, theName, theTag in

    return Person(birthYear: bYear, name: theName, tag: theTag + " Closure")

}

print(person)


The output:

Person(birthYear: 1900, name: "John", tag: "tag Builder One")

Person(birthYear: 1900, name: "John", tag: "tag Builder Two")

Person(birthYear: 1900, name: "John", tag: "tag Builder Three")

Person(birthYear: 1910, name: "Mary", tag: "tag Closure")

Saturday, June 11, 2022

Scroll View - Interface Builder

 Here are the steps to make a Scroll View that has an embedded UIView that specifies the height of the contents of the scrollable area.

Create a new app with a View Controller.

Place a Scroll View inside the View Controller.




Constrain the position of the Scroll View. This example will position the Scroll View to use most of the available area. Just use the sizing handles and make the Scroll View fill the parent view. You can choose any size you want. Constrain the scroll view to the Safe Area.


Next place a UIView inside the Scroll View. This example will use a "Container View".







Use the sizing handles to make the Container View the same size as the Scroll View.






Constrain the Container View to the Scroll View's "Content Layout Guide". Constrain leading, top, trailing, and bottom and set all of their values to"0".








Now constrain the Container View to the Scroll View's "Frame Layout Guide". Constrain to "Equal Widths" and set the multiplier value of the constraint to "1".




This example will show vertical scrolling. Therefore set the height of the Container View to something larger than the Scroll View.


This example sets the height to "1200".



Before testing this, place a label somewhere near the bottom of the Container View.



Now run it in the iPhone Simulator. This example uses an iPhone 8.

Drag in the simulator to see the label.




And that's it!

Monday, May 30, 2022

StackView - Constraining Contained Views

 These are the steps to layout a StackView's contained views by constraining the height of the contained views.


Step 1: Add Vertical Stack View


Step 2: Constrain the Stack View


Step 3: Add UIView to the Stack View


Step 4: Duplicate the UIView so that there are three UIViews


Step 5: Set the background color of each UIView to a unique color.


Step 6: Constrain the top UIView to the bottom UIView in the stack. Set to "Equal Heights"


Step 7: Constrain the middle UIView to the bottom UIView in the stack. Set to "Equal Heights"


Step 8: Change the middle UIView's constraint multiplier to 1.0



Step 9: Change the top UIView's constraint multiplier to 0.5

Sunday, May 29, 2022

UIViewExtended - StackView and FillProportionally

 Hav you have ever put a UIView in a StackView and wondered why "Fill Proportionally" wasn't working when you set the UIView's "Intrinsic Size"?

It's because the UIView doesn't let the intrinsic value be set, and therefore it can't be read.

So, I am thinking, there must be a way to add a property to the UIView via a subclass that will get the job done.

The approach is to use @IBInspectable.

Here is the source:


//

//  UIViewExtended.swift

//

//  Created by Geoffrey Slinker on 5/28/22.

//  Copyright © 2022 Geoffrey Slinker. All rights reserved.

//


import Foundation

import UIKit


class UIViewExtended : UIView {

    

    enum Keys: String {

      case proportionalSize = "ProportionalSize"

    }

    

    var proportionalSize : CGSize

    

    

    override init(frame: CGRect) {

        proportionalSize = CGSize(width:1.0, height:1.0)

        super.init(frame:frame)

    }

    

    required init?(coder aDecoder: NSCoder) {

        let tempSize : CGSize? = aDecoder.decodeCGSize(forKey: Keys.proportionalSize.rawValue) as? CGSize

        if tempSize != nil {

            proportionalSize = tempSize!

        }

        else {

            proportionalSize = CGSize(width:1.0, height:1.0)

        }

        super.init(coder: aDecoder)

    }

    

    override func encode(with coder: NSCoder) {

        coder.encode(self.proportionalSize, forKey: Keys.proportionalSize.rawValue)

        super.encode(with: coder)

    }

    

    @IBInspectable var intrinsicSize: CGSize {

        get {

            return proportionalSize

        }

        set {

            proportionalSize = newValue

        }

    }

    

    override open var intrinsicContentSize : CGSize {

        get {

            return proportionalSize

        }

    }

    

}


Just set the intrinsic size in IB (Interface Builder). If you want to have the simulated view in IB look the same, keep the custom intrinsic size the same as the intrinsic size placeholder values. Yes, you have to manually keep the values the same, but at least you can do it all in IB.




Set the values here:







If you want to UI to simulate the proportions then keep the custom value the same as the Intrinsic Size Placeholder value.




Here is the unit test. The unit test also gets the XML so that it can be examined in the debugger.


func testUIViewExtended() throws {

        let view : UIViewExtended = UIViewExtended.init(frame: CGRect(x: 0,y: 0, width: 100, height: 50))

        

        view.intrinsicSize = CGSize(width: 5,height: 5)

        

        

        //Look at the xml in the debugger

        let archiver = NSKeyedArchiver(requiringSecureCoding: false)

        archiver.outputFormat = .xml

        archiver.encodeRootObject(view)

        let someData = archiver.encodedData

        let stringData = String(decoding: someData, as: UTF8.self)


        

        let codedData = try! NSKeyedArchiver.archivedData(withRootObject: view,

                                                            requiringSecureCoding: false)

        

        

        let result = try! NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(codedData) as?

            UIViewExtended

        

        XCTAssert(result?.frame.width == 100)

        XCTAssert(result?.frame.height == 50)


        XCTAssert(result?.intrinsicSize.width == 5)

        XCTAssert(result?.intrinsicSize.height == 5)


    }


See also:


StackView - Constraining Contained Views