Showing posts with label iOS. Show all posts
Showing posts with label iOS. 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.



















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

Saturday, January 02, 2016

Objective C and JSON, Convert a subclass of NSObject with properties that are other NSObjects to JSON

Check out GSObject on GitHub.

I have been investigating the serialization of Objective-C classes to JSON and have found that it is not as easy as I had hoped.

So, I wrote my own. Just like any good developer would do. ;-)

My solution is very simple and I was surprised at how simple it could be done compared to all of the solutions that I found out there.

The solution is based on these simple methods:


- (NSDictionary<NSString *,id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys


+ (id)JSONObjectWithData:(NSData *)data options:(NSJSONReadingOptions)opt error:(NSError * _Nullable *)error


- (id)valueForKey:(NSString *)key

- (void)setValue:(id)value forKey:(NSString *)key

By overriding the valueForKey: and setValue:forKey in the new base class GSObject and then using some reflection/introspection and the dictionaryWithValuesForKeys: and JSONObjectWIthData:options:error I was able to write this simple and handy code.

Enjoy!


# GSObject

GSObject is a class that allows you to serialize to JSON or deserialize from JSON.

GSObject is a subclass of NSObject.
GSObject overrides valueForKey and setValue:forKey

GSObject also looks for the following SELECTORs:
- (NSString *) jsonValue
+ (NSNumber *) initWithJsonValue : (NSString *) jsonValue

These SELECTORS allow you to create subclasses or categories to control the JSON. Examples of usages would be with NSDate or NSNumber as currency.

Following are some simple classes to use as an example:


//
//  ThingOne.h
//  JasonStuff
//
//  Created by Geoffrey Slinker on 12/28/15.
//  Copyright © 2015 Slinkworks LLC. All rights reserved.
//

#import
#import "GSObject.h"
#import "ThingTwo.h"

@interface ThingOne : GSObject

@property (nonatomic, retain) NSString *name;
@property (nonatomic, retain) ThingTwo *thingTwo;
@property (nonatomic, retain) NSArray *values;
@property (nonatomic, retain) NSDictionary *dict;
@property int myInt;
@property float myFloat;
@property BOOL myBool;
@property (nonatomic, retain) NSNumber* someMoney;


@end

-----------------------------------------------------------------------

//
//  ThingOne.m
//  JasonStuff
//
//  Created by Geoffrey Slinker on 12/28/15.
//  Copyright © 2015 Slinkworks LLC. All rights reserved.
//

#import "ThingOne.h"

@implementation ThingOne

@synthesize name;
@synthesize thingTwo;
@synthesize values;
@synthesize dict;
@synthesize myInt;
@synthesize myFloat;
@synthesize myBool;
@synthesize someMoney;

- (instancetype)init
{
self = [super init];

thingTwo = [[ThingTwo alloc] init];

thingTwo.stuff = @"Thing Two Stuff";
thingTwo.someOtherStuff = @"Thing Two Other Stuff";
NSDateFormatter *dateFormater = [[NSDateFormatter alloc]init];
[dateFormater setDateFormat:@"yyyy-mm-dd"];
thingTwo.someDate =  [dateFormater dateFromString:@"1963-10-07"];

values = [NSArray arrayWithObjects:@"Value1", @"Value2", @"Value3", nil];

dict = [NSDictionary dictionaryWithObjectsAndKeys:@"value1", @"key1", @"value2", @"key2", nil];

myInt = 5431;
myFloat = 123.456f;
myBool = YES;

someMoney = [NSNumber numberWithInt:503];

return self;
}

@end

-----------------------------------------------------------------------

//
//  ThingTwo.h
//  JasonStuff
//
//  Created by Geoffrey Slinker on 12/28/15.
//  Copyright © 2015 Slinkworks LLC. All rights reserved.
//

#import
#import "GSObject.h"

@interface ThingTwo : GSObject

@property (nonatomic, retain) NSString *stuff;
@property (nonatomic, retain) NSString *someOtherStuff;
@property (nonatomic, retain) NSDate *someDate;
@property (nonatomic, retain) NSString *nullString;
@property (nonatomic, retain) NSDate *nullDate;

@end

-----------------------------------------------------------------------

//
//  ThingTwo.m
//  JasonStuff
//
//  Created by Geoffrey Slinker on 12/28/15.
//  Copyright © 2015 Slinkworks LLC. All rights reserved.
//

#import "ThingTwo.h"

@implementation ThingTwo

@synthesize stuff;
@synthesize someOtherStuff;
@synthesize someDate;

- (instancetype)init
{
self = [super init];

someDate = [NSDate date];

return self;
}

@end

-----------------------------------------------------------------------

Here is some example code on how to use it:

ThingOne* object1 = [[ThingOne alloc] init];
object1.name = @"John Jones";


NSData* jsonData1 = [object1 toJsonDataWithOptions:NSJSONWritingPrettyPrinted];
NSString *jsonString1 = [object1 toJsonStringWithOptions:NSJSONWritingPrettyPrinted];

NSDictionary *dict1 =  [GSObject dictionaryWithValues:object1];

NSString *roundTripJson1 = [object1 toJsonStringWithOptions:NSJSONWritingPrettyPrinted];

ThingOne *object2 = [GSObject create:[ThingOne class] fromJsonString:roundTripJson1];
NSString *roundTripJson2 = [object2 toJsonStringWithOptions:NSJSONWritingPrettyPrinted];


NSString *json3 = @"{\"myInt\" : 2039,\"thingTwo\" : {\"stuff\" : \"Thing Two Stuff2\",\"someOtherStuff\" : \"Thing Two Other Stuff2\"},\"name\" : \"William Smith\",\"dict\" : {\"key10\" : \"value10\",\"key20\" : \"value20\"},\"values\" : [\"ValueA\",\"ValueB\",\"ValueC\"]}";

ThingOne *object3 = [GSObject create:[ThingOne class] fromJsonString:json3];
NSDictionary *dict3 =  [GSObject dictionaryWithValues:object3];
NSString *roundTripJson3 = [object3 toJsonStringWithOptions:NSJSONWritingPrettyPrinted];

-----------------------------------------------------------------------
Here are the variables from the debugger:



(lldb) po jsonString1
{
  "values" : [
    "Value1",
    "Value2",
    "Value3"
  ],
  "myInt" : 5431,
  "myFloat" : 123.456,
  "myBool" : true,
  "someMoney" : "$503.00",
  "thingTwo" : {
    "stuff" : "Thing Two Stuff",
    "nullDate" : null,
    "someDate" : "1963-01-07 07:10:00 +0000",
    "nullString" : null,
    "someOtherStuff" : "Thing Two Other Stuff"
  },
  "name" : "John Jones",
  "dict" : {
    "key1" : "value1",
    "key2" : "value2"
  }
}

(lldb) po roundTripJson1
{
  "values" : [
    "Value1",
    "Value2",
    "Value3"
  ],
  "myInt" : 5431,
  "myFloat" : 123.456,
  "myBool" : true,
  "someMoney" : "$503.00",
  "thingTwo" : {
    "stuff" : "Thing Two Stuff",
    "nullDate" : null,
    "someDate" : "1963-01-07 07:10:00 +0000",
    "nullString" : null,
    "someOtherStuff" : "Thing Two Other Stuff"
  },
  "name" : "John Jones",
  "dict" : {
    "key1" : "value1",
    "key2" : "value2"
  }
}

(lldb) po roundTripJson2
{
  "values" : [
    "Value1",
    "Value2",
    "Value3"
  ],
  "myInt" : 5431,
  "myFloat" : 123.456,
  "myBool" : true,
  "someMoney" : "$503.00",
  "thingTwo" : {
    "stuff" : "Thing Two Stuff",
    "nullDate" : null,
    "someDate" : "1963-01-07 07:10:00 +0000",
    "nullString" : null,
    "someOtherStuff" : "Thing Two Other Stuff"
  },
  "name" : "John Jones",
  "dict" : {
    "key1" : "value1",
    "key2" : "value2"
  }
}

(lldb) po roundTripJson3
{
  "values" : [
    "ValueA",
    "ValueB",
    "ValueC"
  ],
  "myInt" : 2039,
  "myFloat" : 123.456,
  "myBool" : true,
  "someMoney" : "$503.00",
  "thingTwo" : {
    "stuff" : "Thing Two Stuff2",
    "nullDate" : null,
    "someDate" : "1963-01-07 07:10:00 +0000",
    "nullString" : null,
    "someOtherStuff" : "Thing Two Other Stuff2"
  },
  "name" : "William Smith",
  "dict" : {
    "key10" : "value10",
    "key20" : "value20"
  }
}

(lldb)


Monday, January 13, 2014

Objective C - Iterate over class properties and access property by class type for iOS

I needed a way to add an input accessory on some of my view controllers that have properties for many UITextView and UITextField properties. I didn't want to do it one at a time, I could miss one, or if I added or deleted one I would have to fix that manually as well.

So, I needed a way to walk the properties of my view controller, find all of the UITextView and UITextField properties, and call the method to set the input accessory view.

Here is the resulting code:


+ (NSArray *)allPropertyNamesFor: (id)target
{
    unsigned count;
    objc_property_t *properties = class_copyPropertyList([target class], &count);
    
    NSMutableArray *results= [NSMutableArray array];
    
    unsigned i;
    for (i = 0; i < count; i++)
    {
        objc_property_t property = properties[i];
        NSString *name = [NSString stringWithUTF8String:property_getName(property)];
        [results addObject:name];
    }
    
    free(properties);
    
    return results;
}



+ (void) attachToSubViewsOf: (NSObject *) target withAccessoryView: (UIView *)accesoryView {
    
    NSArray *properties = [self allPropertyNamesFor:target];
    
    for (NSString *propertyName in properties) {
        
        SEL sel =  NSSelectorFromString(propertyName);
        
        if ([target respondsToSelector:sel]) {
            
            id results = [target performSelector:sel withObject:nil];
            
            if( ([results isKindOfClass:[UITextView class]]) || ([results isKindOfClass:[UITextField class]]) ) {
                
                SEL setInputAccessoryViewSel = sel_registerName("setInputAccessoryView:");
                if ([results respondsToSelector:setInputAccessoryViewSel]) {
                    
                    [results performSelector:setInputAccessoryViewSel withObject:accesoryView];
                }
                
            }

        }
        
    }
    
}