Dailycode.info

Short solution for short problems

SUDZC: Saving arrays of objects.

SUDZC works pretty fine out of the box. You will defenately change here and there some things in the generated code. But probably the worst thing most people will end up bumping against is the fact that it doesn't support saving array of objects to the database. Its easy to retrieve arrays of objects, but for some reason the development stopped there and didn't spend any time any more to the sending of SOAPArrays. In my application I had to save objects in 1 statement and process them on the server synchronously. 

This required a deeper implementation and I had to inspect the SOAP envelope that needed to be generated. By default, the SUDZC will generate something like this:

<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns="http://tempuri.org/"><soap:Body><SaveOrderDetails></SaveOrderDetails></soap:Body></soap:Envelope>

Not much use, there even is no data from the objects passed. The envelope only has the Method call and that's it.

The desired result at the end should look like this:

<?xml version="1.0" encoding="utf-8"?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"  xmlns:tem="http://tempuri.org/" xmlns:ser="http://schemas.microsoft.com/2003/10/Serialization/"  xmlns:a="http://schemas.datacontract.org/2004/07/SFIMobileEntities" ><soapenv:Body><tem:SaveOrderDetails><tem:f06Details><a:GP_MES_OrderDetails><a:ChangeTracker><a:State>Unchanged</a:State></a:ChangeTracker><a:Client>900</a:Client><a:Created>2012-12-27T12:33:58.697</a:Created><a:CreatedBy>@CORDOBA</a:CreatedBy><a:Description>u</a:Description><a:Modified>2012-12-28T09:55:07.550</a:Modified><a:ModifiedBy>@CORDOBA</a:ModifiedBy><a:Plant>GX</a:Plant><a:Pos>1</a:Pos><a:ProductCat>t</a:ProductCat><a:Quantity>10</a:Quantity><a:SalesOrder>119000764</a:SalesOrder><a:Sequence>1</a:Sequence><a:Size>ào</a:Size><a:StoragePlace>à</a:StoragePlace><a:UpdCount>10</a:UpdCount></a:GP_MES_OrderDetails><a:GP_MES_OrderDetails><a:ChangeTracker><a:State>Unchanged</a:State></a:ChangeTracker><a:Client>900</a:Client><a:Created>2012-12-27T12:33:58.697</a:Created><a:CreatedBy>@CORDOBA</a:CreatedBy><a:Description>u</a:Description><a:Modified>2012-12-28T09:55:07.557</a:Modified><a:ModifiedBy>@CORDOBA</a:ModifiedBy><a:Plant>GX</a:Plant><a:Pos>2</a:Pos><a:ProductCat>t</a:ProductCat><a:Quantity>20</a:Quantity><a:SalesOrder>119000764</a:SalesOrder><a:Sequence>2</a:Sequence><a:Size>àoo</a:Size><a:StoragePlace>àpp</a:StoragePlace><a:UpdCount>10</a:UpdCount></a:GP_MES_OrderDetails><a:GP_MES_OrderDetails><a:ChangeTracker><a:State>Unchanged</a:State></a:ChangeTracker><a:Client>900</a:Client><a:Created>2012-12-27T12:33:58.697</a:Created><a:CreatedBy>@CORDOBA</a:CreatedBy><a:Description>u</a:Description><a:Modified>2012-12-28T09:55:07.560</a:Modified><a:ModifiedBy>@CORDOBA</a:ModifiedBy><a:Plant>GX</a:Plant><a:Pos>3</a:Pos><a:ProductCat>t</a:ProductCat><a:Quantity>30</a:Quantity><a:SalesOrder>119000764</a:SalesOrder><a:Sequence>3</a:Sequence><a:Size>ào</a:Size><a:StoragePlace>à</a:StoragePlace><a:UpdCount>11</a:UpdCount></a:GP_MES_OrderDetails><a:GP_MES_OrderDetails><a:ChangeTracker><a:State>Unchanged</a:State></a:ChangeTracker><a:Client>900</a:Client><a:Created>2012-12-27T12:33:58.697</a:Created><a:CreatedBy>@CORDOBA</a:CreatedBy><a:Description>u</a:Description><a:Modified>2012-12-28T09:55:07.567</a:Modified><a:ModifiedBy>@CORDOBA</a:ModifiedBy><a:Plant>GX</a:Plant><a:Pos>4</a:Pos><a:ProductCat>t</a:ProductCat><a:Quantity>40</a:Quantity><a:SalesOrder>119000764</a:SalesOrder><a:Sequence>4</a:Sequence><a:Size>ào</a:Size><a:StoragePlace>à</a:StoragePlace><a:UpdCount>12</a:UpdCount></a:GP_MES_OrderDetails><a:GP_MES_OrderDetails><a:ChangeTracker><a:State>Unchanged</a:State></a:ChangeTracker><a:Client>900</a:Client><a:Created>2012-12-27T12:33:58.697</a:Created><a:CreatedBy>@CORDOBA</a:CreatedBy><a:Description>u</a:Description><a:Modified>2012-12-28T09:55:07.570</a:Modified><a:ModifiedBy>@CORDOBA</a:ModifiedBy><a:Plant>GX</a:Plant><a:Pos>5</a:Pos><a:ProductCat>t</a:ProductCat><a:Quantity>50</a:Quantity><a:SalesOrder>119000764</a:SalesOrder><a:Sequence>5</a:Sequence><a:Size>à</a:Size><a:StoragePlace>à</a:StoragePlace><a:UpdCount>8</a:UpdCount></a:GP_MES_OrderDetails><a:GP_MES_OrderDetails><a:ChangeTracker><a:State>Unchanged</a:State></a:ChangeTracker><a:Client>900</a:Client><a:Created>2012-12-27T14:15:15.517</a:Created><a:CreatedBy>@CORDOBA</a:CreatedBy><a:Description>u</a:Description><a:Modified>2012-12-28T09:55:07.570</a:Modified><a:ModifiedBy>@CORDOBA</a:ModifiedBy><a:Plant>GX</a:Plant><a:Pos>6</a:Pos><a:ProductCat>t</a:ProductCat><a:Quantity>60</a:Quantity><a:SalesOrder>119000764</a:SalesOrder><a:Sequence>6</a:Sequence><a:Size>io</a:Size><a:StoragePlace>p</a:StoragePlace><a:UpdCount>12</a:UpdCount></a:GP_MES_OrderDetails><a:GP_MES_OrderDetails><a:ChangeTracker><a:State>Unchanged</a:State></a:ChangeTracker><a:Client>900</a:Client><a:Created>2012-12-27T15:07:12.430</a:Created><a:CreatedBy>@CORDOBA</a:CreatedBy><a:Description>u</a:Description><a:Modified>2012-12-28T09:55:07.573</a:Modified><a:ModifiedBy>@CORDOBA</a:ModifiedBy><a:Plant>GX</a:Plant><a:Pos>7</a:Pos><a:ProductCat>t</a:ProductCat><a:Quantity>70</a:Quantity><a:SalesOrder>119000764</a:SalesOrder><a:Sequence>7</a:Sequence><a:Size>tyo</a:Size><a:StoragePlace>ty</a:StoragePlace><a:UpdCount>4</a:UpdCount></a:GP_MES_OrderDetails></tem:f06Details></tem:SaveOrderDetails></soapenv:Body></soapenv:Envelope>

That's something else...

How do we get there. The first thing we will need to do is to add an extra method on our soap class. (soap.h and soap.m)

In the soap.h (add these lines):

//Created by MRD to serialize arrays

+ (NSString*) serializeForArray: (id) object withName: (NSString*) nodeName;

In the soap.m (ass these lines):

// Serializes an object to a string, XML representation with a specific node name.

// if its an array of objects, it will use the serializeForArray implementation on the object array to serialize.

+ (NSString*) serialize: (id) object withName: (NSString*) nodeName

{

 

    if([object respondsToSelector:@selector(serialize:)])

    {

        return [object serialize: nodeName];

}

    else

    {

        if([object isKindOfClass:[NSArray class]])

        {

            

            NSMutableString* s = [NSMutableStringstring];

            

            bool setFirstNode=NO;

            for(id obj in object)

            {

                if (!setFirstNode)

                {

                    [s appendFormat:@"<tem:%@>",nodeName];

                    setFirstNode=YES;

                }

                if([obj respondsToSelector:@selector(serializeForArray:)])

                {

                    NSMutableString * str = [obj serializeForArray:nil];

                    [s appendFormat:@"%@",str];

                }

                else

                {

                    [s appendFormat:@"<%@>%@</%@>",@"arr:string",obj,@"arr:string"];

                }

            }

            if (setFirstNode)

            {

                [s appendFormat:@"</tem:%@>",nodeName];

            }

            else

            {

                [s appendFormat:@"</tem:%@>",nodeName];

            }

            return s;

            

        }

    }

    return [NSString stringWithFormat:@"<%@>%@</%@>", nodeName, [Soap serialize: object], nodeName];

}

When we have a class that inherits from the SOAPArray, we ill now not call the default serialize method, but the serializeForArray method. This is done by overwriting the SoapArray function call in your SUDZC generated array. The xml that was generated was working only for 1 object. So we needed to add first the parameter name and then the iteration of the objects. In the method below you'll see that first I will add the parameter name with the tem prefix (again inspect your soap call to check of you are using the same namespace. You could do this with SOAPUI by SmartBear.)

You simply add this method (e.g. in the implementation MYArrayOfGP_MES_OrderDetails):

- (NSMutableString *) serialize:(NSString *)nodeName

{

    NSMutableString *str = [NSMutableStringstring];

    [str appendFormat: @"<tem:%@", nodeName];

    [str appendString: [selfserializeAttributes]];

    [str appendString: @">"];

    for (id content in self)

    {

        [str appendString:[Soap serializeForArray:content withName:nodeName]];

    }

    [str appendFormat: @"</tem:%@>", nodeName];

    return str;

}

 

Now this is where the difference is. We will generate a different SOAP body when the object is used in an array. So the last thing we need to do is to implement this function on the object itself.You could even loose the argument nodeName since its not used in this function. (In my case: implementation GALVGP_MES_OrderDetails)

    - (NSMutableString*) serializeForArray: (NSString*) nodeName

    {

        NSMutableString* s = [NSMutableStringstring];

        [s appendString:@"<a:GP_MES_OrderDetails>"];

        [s appendString: [self serializeElements]];

        [s appendString:@"</a:GP_MES_OrderDetails>"];

        return s;

    }

Once you've done this, you can send array of your object to the server and use them there. It works fine. If you still struggle, I could help you with the complete solution, because there are some things I've had to change on the CreateEnvelope for Saving data. The default SUDZC code didn't work for me, since my SOAP required some extra namespaces.

This involved creating a new CreateEnvelope method in the SOAP class. This method should then be called when you do insert or update. This make sure that the namespace for the object prefixes is passed. The method looks like this:

NSString* const SOAP_PREFIX = @"soapenv";

NSString* const NS_PREFIX = @"tem";

NSString* const SER_PREFIX_URL = @"xmlns:ser=\"http://schemas.microsoft.com/2003/10/Serialization/\"";

 

NSString* const HTTP_PREFIX = @"http://";

NSUInteger const FORWARD_FLASH_CHARACTER_VALUE = 47;

// Creates the XML request for the SOAP envelope with optional SOAP headers and extra namespace.

+ (NSString*) createEnvelope: (NSString*) method forNamespace: (NSString*) ns

forParameters: (NSString*) params withHeaders: (NSDictionary*) headers withExtraNameSpace: (NSString *) extraNameSpace

{

    

    NSMutableString* s = [NSMutableStringstring];

 

    [s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];

 

    [s appendString: @"<"];

    [s appendString: SOAP_PREFIX];

    [s appendString:@":Envelope xmlns:"];

    [s appendString: SOAP_PREFIX];

    [s appendString: @"=\"http://schemas.xmlsoap.org/soap/envelope/\" "];

    [s appendString:@" xmlns:"];

    [s appendString: NS_PREFIX];

    [s appendFormat:@"=\"%@\" ",ns ];

    [s appendFormat:@"%@ ",SER_PREFIX_URL];

    if(extraNameSpace)

    {

        [s appendFormat:@" %@ >",extraNameSpace];

    }

    

    if(headers != nil && headers.count > 0) {

        [s appendString: @"<"];

        [s appendString: SOAP_PREFIX];

        [s appendString:@":Header>"];

        

        for(id key in [headers allKeys]) {

            if([[headers objectForKey: key] isMemberOfClass: [SoapNil class]])

            {

                [s appendFormat: @"<%@ xsi:nil=\"true\"/>", key];

            } else {

                [s appendString:[Soap serializeHeader:headers forKey:key]];

            }

        }

        [s appendString: @"</"];

        [s appendString: SOAP_PREFIX];

        [s appendString:@":Header>"];

    }

    

    [s appendString: @"<"];

    [s appendString: SOAP_PREFIX];

    [s appendString:@":Body>"];

    

    NSMutableString* fullMethodName = [NSMutableStringstring];

    [fullMethodName appendString:NS_PREFIX];

    [fullMethodName appendString:@":"];

    [fullMethodName appendString:method];

    

    [s appendFormat: @"<%@>%@</%@>", fullMethodName,[params

                                                     stringByReplacingOccurrencesOfString:@"&" withString:@"&amp;"],

     fullMethodName];

    

    

    [s appendString: @"</"];

    [s appendString: SOAP_PREFIX];

    [s appendString:@":Body>"];

    [s appendString: @"</"];

    [s appendString: SOAP_PREFIX];

    [s appendString:@":Envelope>"];

    return s;

}

I used a tool called soapui to inspect the SOAP and find out what the envelope should look like.  


Using nullable/nilable types with iOS

It's obvious that iOS doesn't support nilable or nullable(.Net) types. I started having problems becuase we have a .Net implementation (server side) where we have bool? So on client side it resulted in a YES/NO/N/A choice. In iOS its a Segmented Control that has these options. But when my Entities are converted to iOS objects, they result offcourse in simple types: bool. So this means that its by default no. How to solve this? I could change my objects on server side and regenerate them. But since I'm using the entity framework generation, I do not want to do this. Then my collegue pointed me in the good direction. Using partial classes on server side eventually did the trick. So by extending the base class and adding properties I got a working solution. I will describe it below with some points to pay attention to.

So on iOS site I want int in stead of bool. Int value 0 = YES, 1 = NO and 2 = null. To get this I created some methods on the .Net WCF services. Here is an example of the extension class I used:

public partial class GP_MES_F06ByOrder

{

    [DataMember]

    public int ICHolesNS

    {

        get

        {

            return GalvEntFunctions.GetIntFromBool(this.ICHoles);

        }

        set

        {

            this.ICHoles = GalvEntFunctions.GetBoolFromInt(value);

        }

    }

 

    [DataMember]

    public int ICDoubleWeldNS

    {

        get

        {

            return GalvEntFunctions.GetIntFromBool(this.ICDoubleWeld);

        }

        set

        {

            this.ICDoubleWeld = GalvEntFunctions.GetBoolFromInt(value);

        }

    }

 

The functions to convert the int to bool and bool to int I used:

public class GalvEntFunctions

{

    public static bool? GetBoolFromInt(int value)

    {

        switch (value)

        {

            case 0: return true;

            case 1: return false;

            default: return null;

        }

    }

 

    public static int GetIntFromBool(bool? value)

    {

        switch (value)

        {

            case true: return 0;

            case false: return 1;

            default: return 2;

        }

    }

}

Now these properties are available on you entities. Mind the name giving. Thats important. Because the serializer will serialize and deserialize in the order of the properties. So in my case cince I'm using it for iOS I added NS at the end to make sure this property is de/serialized after the original property. In my iOS client I only use the NS properties to get what I want.

Now you can use the default Entity generation without messing around in the code afterwards.

On iOS side I can now implement these nullable types very easy with my segmented controls:

    self.selOpeningHolesOK.selectedSegmentIndex = self.currentF06.ICHolesNS;

    self.selLoadingPosibilities.selectedSegmentIndex = self.currentF06.ICLoadingPos;


.Net Self Tracking Entities and JSON

I know this is not the ideal situation, better to use POCO's or simple objects. But when you already have a large structure in place using these self tracking entities, it can be usefull to do this. In my case we had webservices build for .Net client applications. Now we want to reuse this architecture and consume it on IPAD and IPHONE. Can this work? Yes offcourse. First I tried with SOAP, which works well, but there were 3 problems. First I was relying on a object generator tool of SUDZC, second SOAP is taking more data then JSON, third, iOS doesn't support nillable and the generator converted it into simple types. So now I decided to use JSON, use my own objects and reuse the webservices. 

This involved making a new service layer the consumes the same process layer as my .Net WCF services.

But you will notice that when you try to pass a self tracking entity in JSON format, the webservice will crash. 

The solution to this was:

Set the IsReference flag to false, and remove the DataMember attribute from the ChangeTracker.

//[DataMember]

public ObjectChangeTracker ChangeTracker

and

[DataContract(IsReference = false)]

public partial class GP_MES_Clients: IObjectWithChangeTracker, INotifyPropertyChanged

After this all is changed, you can consume this service in your iOS application!


iOS : Static UITableView scrolling stops working

I had this problem: I was using a static UITableView in a UITabBarController. The table scrolling worked fine the first time I arrived on the View. But when I navigated to another view and back to my static table, the scrolling suddenly stopped working. I could only see the top of the table that fits the screen. When I tried to scroll it just gave me the bounces.

It took me a while of googling without finding someone with the same problem. So I tried out myself and figured out that reloading the table data in the viewDidAppear fixes the problem (Do not forget to make an outlet for your static table, mines called tableContents):

- (void) viewDidAppear:(BOOL)animated

{

    [self.tableContentsreloadData];

}


iOS: Change Navigation Item at runtime in the UITabBarController

The problem was that I had a navigation item that had to point to different view controllers depending on runtime data. So I looked for several solutions. But when you want to keep the navigation tab bar items and functionality, it was not so easy. So I came up with the following solution. The way to do it is really simple. When you drag the ViewControllers to the TabBarController in the Story
Board, these controllers are added to the viewControllers collection of the UITabBarController. We Simply remove the ones we don't need at runtime: 

I added 2 identical navigation items to the tab bar. At runtime I will hide the one that is not used.

 

So now I just check the property in the UITabBarController class and hide the item that is not used in the viewDidLoad:

NSMutableArray * viewControllers = [[NSMutableArrayalloc] initWithArray:self.viewControllers] ;

    if (viewControllers)

    {

        //Checken op plant parameter, en zodoende de juiste incoming inspection form tonen.

        VariableStore * store = [VariableStoresharedInstance];

        [viewControllers removeObjectAtIndex:store.PPIncomingInspectionForm];

        [self setViewControllers:viewControllers];

    }

The result looks like this:

 

 


iOS : duplicate interface definition for class

I had a problem. It was a small search effort, but I could solve it without creating a new project etc...

The thing was I had a Class B that was importing Class A.

Then I had a class that imported Class B and also Class A.

When I did this, these problems occured. 

Eg. A SOAP webservice Class imports all the Entities that are passed over the web.

 

Class goToSchoolWebservice.

#import "person.h"

#import "school.h"

...

 

Then I had a Singleton class used for caching that had the Logged in Person and also a ref to the webservice class.

Class variableStore

#import "person.h"

#import "goToSchoolWebservice.h"

 

--> this is where is went wrong!!

So watch out for these circular references. ITs not so easy to detect them!


iOS : Simple logon procedure

Here a simple example to do a login check for your app. It compares the entered password with a password stored in the variable cache:

- (IBAction)btnLogin:(id)sender

{

    VariableStore *store = [VariableStoresharedInstance];

    NSComparisonResult res = [store.User.Password compare: self.txtPWD.text];

    

    if (res == NSOrderedSame)

    {

        //Do segue

        [selfperformSegueWithIdentifier:@"LoginIN"sender:self];

    }

    else

    {

        //Show wrong pwd

        self.lblError.text = @"Wrong password!";

    }

}